I Let an AI Coding Agent Write My Terraform. It Broke My State File. Here's the Post-Mortem.

July 10, 2026

This is a post-mortem for an incident that, in the end, didn’t take anything down — I caught it at the plan stage, one line before I would have typed “yes.” I’m writing it up anyway because the near-miss is the useful part: it shows exactly where the process broke, and that’s more actionable than a story about something that actually blew up.

What happened

I asked an AI coding agent to refactor a Terraform module — pull a database resource out of a monolithic main.tf into its own file, and rename it from aws_db_instance.main to aws_db_instance.primary to match a naming convention I’d asked it to apply across the module. Reasonable ask, mechanical change.

The agent moved the code correctly. The HCL was fine. What it didn’t do — because I hadn’t told it to, and because it has no memory of what’s actually in my state file — was account for the fact that renaming a resource block in configuration doesn’t rename it in state. Terraform doesn’t track resources by intent, it tracks them by address. aws_db_instance.main and aws_db_instance.primary are, as far as Terraform’s state file is concerned, two completely unrelated resources.

What the plan actually said

I ran terraform plan out of habit, saw a big diff (expected, given the refactor moved several resources across files), and skimmed it for anything that looked like a real change rather than a reformat. That’s the mistake. Buried in the middle of a plan with dozens of no-op moves was:

  # aws_db_instance.main will be destroyed
  # aws_db_instance.primary will be created

Not a rename. A destroy and a create, of the production database. If I’d applied that, Terraform would have deleted the RDS instance and provisioned a new empty one under the new address, and I would have found out my actual data was gone precisely when the application tried to read it.

Root cause

The AI agent did exactly what I asked, correctly, at the code level. The failure was mine, in two places:

  1. I asked for a rename without telling it the change needed a moved block. The agent has no way to know a resource is stateful and irreplaceable unless that constraint is in the prompt or in project conventions it can see. A rename in HCL is invisible to it as a “state-affecting” operation unless it’s explicitly reasoning about Terraform’s state model, and in this case it wasn’t.
  2. I read the plan output like a diff summary instead of a list of destroys. A 40-line plan with one will be destroyed buried in it looks, at a skim, like a 40-line plan. It isn’t. It’s a 39-line no-op and one action that matters.

Agent renames resource block

terraform plan 40-line diff generated

destroy + create found on line 23

apply cancelled before typing yes

this is the only line that mattered

The fix

Two changes, neither of which involved trusting the agent less in general — both of which involved making the failure mode structurally harder to miss.

prevent_destroy on anything genuinely irreplaceable. The database, the S3 buckets holding user uploads, anything where “recreated” doesn’t mean “back to normal,” now carries a lifecycle block:

resource "aws_db_instance" "primary" {
  # ...

  lifecycle {
    prevent_destroy = true
  }
}

This doesn’t prevent bad plans, it prevents bad applies — Terraform refuses to proceed if the plan includes destroying a protected resource, which turns “I missed it in the diff” from a data-loss incident into a hard stop with an error message.

A standing instruction, not a one-off prompt. Renaming or moving any resource address now comes with an explicit requirement — for the agent and for me — to either write a moved block or run terraform state mv in the same change, and to call out any destroy/create pair in the plan explicitly before I’m asked to approve it. Putting this in the project’s persistent instructions means I don’t have to remember to ask for it every time; it’s part of what “done” means for a refactor like this one.

What I didn’t change

I didn’t stop using AI agents for infrastructure code, and I didn’t add a blanket “no AI on Terraform” rule. The agent produced correct HCL for the ask it was given — the gap was state-model context I hadn’t supplied and a review habit that treated plan output as a diff to skim instead of a list of actions to read line by line. Fixing the actual gap is more useful than removing the tool that exposed it.

The rule I took away from this

Nothing an AI agent generates for infrastructure code gets applied on a diff skim. terraform plan output gets read for verbs — created, destroyed, replaced — not for line count. That was true before AI agents were writing Terraform too; it just used to be easier to get away with skipping it, because I wrote every change myself and had more context for what “should” be in the diff.