Founder pricing: Pro at $9.99/mo forever for the first 50 members. 18 of 50 left Claim yours →
All posts

Terraform and AI coding agents: destroy is only as safe as your state file

The SanuDesk team 6 min read
terraform infrastructure ai agents

On February 26, 2026, an AI coding agent ran terraform destroy against the production infrastructure behind DataTalks.Club, a data engineering course platform. The agent had switched to a new computer mid-migration and started without a local copy of the Terraform state file. It ran terraform plan, saw what looked like a clean slate, and applied a second copy of the entire stack next to the one already serving real traffic. Cleaning up the resulting duplicates, in the agent's own words, would be "cleaner and simpler" through Terraform than through the AWS CLI.

It was right about the tool and wrong about what the tool now believed. Once the original state file resurfaced and gave Terraform, in the founder's own account, "a consistent view of infrastructure," that view included both stacks as equally real. terraform destroy doesn't know which one is the duplicate. It deletes whatever state says exists: the VPC, the ECS cluster, both load balancers, the bastion host, the RDS database, and every automated snapshot, in one run. A single table, courses_answer, held 1,943,200 rows of 2.5 years of course submissions.

The command did exactly what it was told. The state file is what was wrong, and nothing about running the command said so.

Why terraform plan can look clean and still be wrong

A terraform apply against real infrastructure is the operational cousin of a migration against a real database: a clean plan and correct syntax guarantee the tool will do exactly what it's told, never that what it's told is the right thing.

terraform plan refreshes the attributes of every resource already listed in its state file, then shows the difference between that and your configuration. It has no way to notice real infrastructure the state file never mentions. An empty or stale state doesn't read as "something's wrong." It reads as "nothing exists yet," and the plan that follows looks exactly like any other clean, boring plan: a list of resources to create.

That's the gap the agent walked into above. Nothing in the plan output said this already exists somewhere else. Nothing in the apply said you now have two of everything. The only step that looked dangerous was the last one, and by then the decision had already been made two commands earlier, by a state file nobody had checked was the right one.

Three guardrails that look like protection, and their gap

Terraform ships real safety mechanisms. Each one covers a specific failure and misses another one sitting right next to it:

Mechanism Protects against Misses
State locking Two concurrent applies corrupting one shared state Two separate, stale state files that never had to fight over the lock
prevent_destroy A plan that changes a resource in a destructive way Doesn't prevent destroying a resource if you remove its configuration entirely
Manual approval A human reading the plan before typing yes The fiftieth prompt of the day, approved on reflex like the first

None of these are broken. They each protect a narrower slice of "safe" than the name suggests, and an agent optimizing for "get this done" walks straight through the gap between them without noticing there was one.

What happens when two agents share one remote state

HashiCorp's own documentation is clear about what locking actually buys you: it "happens automatically on all operations that could write state" and "prevents others from acquiring the lock." That's real protection against two agents running apply against the same backend at the same moment.

It's also a narrower promise than it sounds. Locking serializes writes to one shared, remote state file. It does nothing for two agents each holding their own stale, unsynced copy, which is closer to what happened above than a locking failure was. Git worktrees solve the equivalent problem for a checkout: every agent gets its own folder and its own branch, nothing to collide over. Terraform state has no worktree. Two sessions pointed at the same infrastructure are pointed at the same ground truth, whether their local copies agree about it or not. It's the same shape of problem as a feature flag nobody remembered to retire: the risk was never the tool, it's state that outlives whoever last checked it.

Where this goes wrong, and what it really costs

A missing state file fails silently, not loudly. No warning, no error, just a plan that reads like a fresh install.

Recovery is luck unless you've tested it. DataTalks.Club came back because AWS support found a snapshot that wasn't even visible in the founder's own console, and restoration still took 24 hours. Not every resource type keeps automated snapshots. Not every provider's support team goes looking for one.

Auto-approve exists because typing "yes" got old. Flags that skip the confirmation prompt remove the one step that might have caught this, for the same reflexive-approval reasons this blog has covered before: the dialog for a safe command and a destructive one looks identical, so attention decays until neither gets read.

The postmortem always names a process, not a bug. The founder's own fix afterward wasn't a smarter agent. It was moving a human back in front of every destructive step: "Every plan is reviewed manually. Every destructive action is run by me."

Give the plan a review lane before anything applies

SanuDesk is a desktop app built around exactly that gap between a plan that reads clean and infrastructure that's real. Your Claude Code, Codex and Gemini sessions tile into one grid, so a terraform plan output is something you read beside the diff that produced it, not something you reconstruct from a state file after the fact. Work arrives through a Kanban board: "migrate the pricing service" is a card, it deploys to a fresh session, and the plan output parks in a review lane before anyone runs apply, one checkpoint between a stale state file and production. Because SanuDesk is bring-your-own-model, that review can run on a cheaper model than the one that wrote the plan. A recurring drift check, running terraform plan against main and flagging anything unexpected, becomes a Loop, journaled per run instead of a habit everyone means to keep. The grid and board are in the free plan (see pricing).

Read your own plan before the next apply

Open the last terraform plan an agent handed you and actually read the resource count before the summary line. If it says more resources to create than you expect, or the state file came from a checkout that's more than a day old, stop there. That mismatch is cheaper to question now than to explain in a postmortem later.

Download SanuDesk free to give every plan a review lane before it applies, or see how the grid, the board and Loops fit together on the features page.

Run your own fleet of agents

SanuDesk is a free desktop app that tiles your Claude Code and Codex sessions and gives every project a Kanban board your agents work from.