Gemini CLI's long context window is the actual reason to reach for it in a Terraform workflow — a plan against a large environment (dozens of modules, hundreds of resources) produces plan JSON that can run past what fits comfortably in a smaller context budget elsewhere, and Gemini 2.5 Pro's 1M-token window swallows it without truncation. This topic covers setup, using that context headroom to summarize large plans, a catch on accidental resource replacement, and an honest side-by-side against Claude Code's output style.
Installing and Connecting Terraform MCP to Gemini CLI
Gemini CLI reads MCP servers from .gemini/settings.json (project) or ~/.gemini/settings.json (user):
{
"mcpServers": {
"terraform-registry": {
"command": "docker",
"args": ["run", "-i", "--rm", "hashicorp/terraform-mcp-server"],
"trust": false
},
"terraform-cli": {
"command": "terraform-mcp-server",
"args": ["--allowed-dirs", "./infra", "--deny-glob", "**/*.tfstate"],
"env": {
"TF_IN_AUTOMATION": "true",
"TF_WORKSPACE": "prod"
},
"trust": false
}
}
}
Leave trust: false on both — Gemini CLI's trust: true skips the confirmation prompt on every tool call from that server, which is the opposite of what you want for anything Terraform-adjacent. Confirm the connection and inspect tools:
gemini
> /mcp list
✓ terraform-registry - 3 tools
✓ terraform-cli - 5 tools
> /mcp desc terraform-cli
terraform_init(working_dir)
terraform_validate(working_dir)
terraform_plan(working_dir, var_file, out)
terraform_state_list(working_dir)
terraform_state_show(working_dir, address)
If terraform_apply doesn't appear in that list at all, good — that means the server binary was built or configured without it, which is the cleanest way to guarantee Gemini CLI can never call it, regardless of prompt or trust settings.
Tips
- Keeptrust: falseon both server entries — Terraform tool calls should always pause for confirmation, especially plan (which touches provider APIs and can be slow/costly against a large environment) and definitely anything apply-adjacent.
- Run/mcp desc <server>after every server config change — Gemini CLI's server restart behavior on config edits isn't always immediate, and you want to confirm the tool list actually reflects your latest--toolsflag.
- If cost matters, note thatterraform_planagainst a provider like AWS makes real API calls to refresh resource state — a plan on a large environment isn't free in time or in provider API rate-limit budget, agent-triggered or not.
Summarizing Large Plan Outputs into Human-Reviewable Change Sets
Here's where the context window actually earns its keep. A monorepo-style Terraform setup with 40+ modules produces plan JSON that can run to tens of thousands of lines. Point Gemini at the whole thing and ask for a structured digest:
Run terraform_plan for infra/environments/prod (var file: prod.tfvars).
Group the full resource_changes list by top-level module, then by
action type (create/update/replace/destroy). For each group, give
me a one-line summary. Call out any module with more than 5 changes
or any replace/destroy action anywhere, in a separate "Needs Review"
section at the top.
A well-structured response for a large plan looks like this — note it leads with what matters, not with plan order:
NEEDS REVIEW (3 items)
- module.database.aws_db_instance.primary: REPLACE
(storage_type gp2 -> gp3 requires no replace normally, but
iops argument was added without throughput, which on this
provider version forces replacement — verify before applying)
- module.networking: 9 changes (threshold exceeded)
- module.legacy_cache.aws_elasticache_cluster.redis: DESTROY
(module.legacy_cache was removed from root — confirm intentional)
BY MODULE (37 unaffected modules omitted for brevity, 0 changes each)
- module.compute: 4 create (new autoscaling group instances)
- module.networking: 6 create, 2 update, 1 destroy (see above)
- module.database: 1 replace (see above)
- module.observability: 2 update (log retention policy change)
- module.legacy_cache: 1 destroy (see above)
Plan: 10 to add, 3 to change, 2 to destroy.
The value here isn't summarization for its own sake — it's the "37 unaffected modules omitted" line and the threshold-based Needs Review section that make a 200-resource plan reviewable in two minutes instead of forty.
terraform plan -json prod.tfvars | jq -r '.resource_changes[].address' | \
awk -F'.' '{print $1}' | sort -u | wc -l
Tips
- Set an explicit numeric threshold ("more than 5 changes") for what counts as needing review — without one, Gemini's definition of "significant" drifts across sessions and plans.
- Ask for unaffected modules to be omitted or collapsed by count, not silently dropped — you want to know 37 modules had zero changes, not just not see them.
- Spot-check the summary's resource counts againstjqon the raw plan JSON occasionally — long-context summarization is generally reliable but not infallible on very large plans, and a silent miscount matters more here than in a 5-resource plan.
Practical Example: Catching an Accidental Resource Replacement in a Plan
The single most common "the agent should have caught this" moment in Terraform work is a resource replacement disguised as an innocuous-looking argument change. Take this real pattern with EBS volumes:
resource "aws_ebs_volume" "data" {
availability_zone = "us-east-1a"
size = 500
type = "gp3"
iops = 4000 # added this line
}
That single added iops line, on a volume that previously had no iops argument set, produces this plan:
# aws_ebs_volume.data must be replaced
-/+ resource "aws_ebs_volume" "data" {
~ id = "vol-0abc123" -> (known after apply)
+ iops = 4000
size = 500
type = "gp3"
# (1 unchanged attribute hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.
That -/+ prefix means replace, and replacing an EBS volume means a new volume ID — anything referencing the old volume ID (an attachment, a snapshot lifecycle policy keyed by ID) breaks, and any data on the volume is gone unless you've planned a snapshot-and-restore. Ask Gemini specifically to hunt for this shape of change, since it's easy to miss when skimming:
In this plan, find every resource where the action is "replace"
(shown as -/+ in text output, or ["delete","create"] in plan JSON).
For each, explain specifically which changed argument forced the
replacement, and whether that's avoidable by using a different
argument or an update strategy.
aws_ebs_volume.data: REPLACE, forced by adding `iops`.
This IS avoidable: EBS supports modifying iops on an existing gp3
volume via the AWS API without recreation. The replacement here is
a Terraform/provider limitation on this attribute combination, not
an AWS constraint — check if a newer aws provider version (6.x)
supports in-place iops modification for gp3 before accepting the
replace. If not, snapshot the volume before applying.
That's the kind of answer worth the setup: it doesn't just flag the replace, it questions whether the replace is even necessary at the infrastructure level versus being a provider modeling limitation — a distinction a junior engineer skimming the diff would likely miss.
Tips
- Explicitly ask the agent to enumerate every-/+(replace) action and name the specific argument that forced it — don't assume it will volunteer this without being asked to search for the pattern.
- When a replace looks avoidable, check the provider changelog for a newer version before accepting the destructive path — provider authors do fix over-eager replacement triggers between minor versions.
- Snapshot or back up any resource facing replacement that holds data (EBS volumes, RDS instances, EFS) before applying, regardless of how confident the agent's explanation sounds.
Comparing Terraform MCP Output Between Gemini CLI and Claude Code
Running the identical plan-review prompt against both tools on the same plan JSON surfaces a real, reproducible difference in default behavior, not just style.
Claude Code tends to produce tighter, more opinionated summaries by default — it will volunteer a recommendation ("schedule this during a maintenance window") without being asked, which is useful when you want a fast read but occasionally means it commits to a judgment call you'd rather make yourself.
Gemini CLI, on the same input, tends to lay out the full categorized breakdown more exhaustively before offering a recommendation, and its recommendations lean more conditional ("if X, then Y; if Z, consider W") rather than a single suggested path. On the 200-resource plan from the earlier example, Gemini's exhaustive-by-default style is the better fit precisely because you want the full module breakdown, not a compressed take. On a small 3-resource plan for a quick sanity check, Claude Code's terser default gets you to a decision faster.
Neither is "more correct" — this is a genuine, verifiable trade-off worth planning your prompting around rather than a claim to take on faith. If you routinely review large multi-module plans, lean on Gemini CLI's context headroom and ask for the exhaustive breakdown. If you're doing quick pre-apply sanity checks on small changesets throughout the day, Claude Code's default terseness costs you less time per check.
terraform show -json tfplan.staging > plan.json
Tips
- Match the tool to plan size: Gemini CLI's context headroom suits large, multi-module plans; Claude Code's terser defaults suit quick single-change sanity checks.
- Don't take either tool's "no issues found" at face value on a large plan without at least one manual spot-check — run the comparison exercise above once per quarter to keep your calibration current as both tools update.
- Standardize the review prompt template (Needs Review threshold, replace-detection instruction) across whichever tool you use, so the comparison between runs — and between tools — is actually apples-to-apples.
Tips
Tips
- Reach for Gemini CLI specifically when plan size is the bottleneck — its context window handles large multi-module plans that would otherwise need chunking elsewhere.
- Always instruct explicit detection of-/+(replace) actions and their forcing argument; this is the single highest-value catch in any plan-review prompt regardless of tool.
- Keeptrust: falseon Terraform MCP servers in Gemini CLI's config — confirmation-per-call is the cheap insurance policy here.