On 26 February 2026, an AI coding agent working on a small team's AWS setup ran terraform destroy with auto-approve. It was working from an old copy of the Terraform state, so it believed the production resources belonged to a stack it was cleaning up. In one command it deleted the production database, the VPC, the ECS cluster, the load balancers, and the bastion host. The database's automated snapshots went with it. Getting the data back took about 24 hours and an upgrade to paid AWS support.
Nothing about that command was unusual. terraform destroy is a normal Terraform command, the agent had working credentials, and nothing between the agent and the cloud said no. That is the gap to close. The fixes below stack: each one catches what the one before it misses.
Why a Prompt Is Not a Control
Most teams start by telling the agent not to destroy anything, in a prompt or an instruction file. That is advice, not enforcement. An agent that hits a confusing state, a failed apply, or resources it thinks are orphaned can decide that destroying and recreating is the cleanest fix, and explain its reasoning while it does it. A control has to sit outside the model: in a hook, in the configuration, in the cloud, or in the credentials.
1. Block the Command Before It Runs
Coding agents that support hooks can run a script before every shell command. A pre-command hook reads the command the agent is about to run, matches destructive patterns such as terraform destroy, tofu destroy, and terraform plan -destroy, and exits with an error so the command never starts. The agent sees the refusal and has to take another route. A pattern only catches the commands it lists, so a hook is the first layer, not the only one.
Two details matter. Install the hook at the user level as well as the project level, so it fires wherever the agent is opened. And give people an explicit way through for the cases where teardown is the point, such as a short-lived sandbox, so nobody is tempted to switch the hook off entirely.
2. Mark Stateful Resources With prevent_destroy
Terraform's prevent_destroy lifecycle setting makes Terraform reject any plan that would destroy that resource, including a plan from terraform destroy:
resource "aws_db_instance" "main" {
# ...
lifecycle {
prevent_destroy = true
}
}
It protects the resource only while the setting stays in the configuration. If the resource block is deleted, or an agent edits the line away, the protection goes with it. That is why it belongs on the resources you cannot recreate, such as databases, buckets with customer data, and KMS keys, and why changes to it should get a human review.
3. Turn On Deletion Protection in the Cloud
Most stateful cloud services can refuse deletion on their own, whatever tool asks. For Amazon RDS, set deletion_protection = true: the database cannot be deleted until someone turns it off with a separate change. Two more settings decide whether you can recover if a delete does get through:
resource "aws_db_instance" "main" {
# ...
deletion_protection = true
skip_final_snapshot = false
final_snapshot_identifier = "main-final"
delete_automated_backups = false
}
In the AWS provider, delete_automated_backups defaults to true, which is how automated snapshots disappear along with an instance. Setting it to false, and keeping a final snapshot, leaves you something to restore from. Backups held outside the stack, for example in AWS Backup, survive even a destroy of the whole configuration.
4. Give the Agent Credentials That Cannot Delete
An agent that writes code does not need permission to delete production. Give it a role that can read and plan, deny Delete* actions on production accounts, and let applies run through a pipeline with its own identity. If the agent does run a destructive command, the cloud API refuses it.
5. Keep State Remote and Locked
The incident started with a stale local copy of the state. Remote state in a locked backend, such as S3 with use_lockfile = true (native locking since Terraform 1.10), means every run works from the same current record, and a second run cannot write at the same time. Turn on versioning for the state bucket so a bad write can be rolled back.
Let Agents Write, Let a Pipeline Apply
Teams that use coding agents for infrastructure tend to land on the same split: the agent writes and validates code, and a pipeline runs plan and apply with policy checks and a human approval. The agent stays fast, and every change that reaches production has a plan someone read and an approver on record.
How ops0 Handles It
The ops0 CLI puts the first layer in place in one step. ops0 init in a Terraform, OpenTofu, or Oxid repo (Oxid is ops0's open-source engine that keeps state in PostgreSQL) installs a pre-command hook that blocks terraform destroy, tofu destroy, oxid destroy, and plan -destroy before they run and records each attempt. To tear down a sandbox on purpose, a person sets OPS0_ALLOW_DESTROY=1 in the environment before starting the session. The hook checks its own environment rather than the command text, so an agent that types the variable into its command is still blocked.
The same setup checks every change the agent writes at the end of its turn: validate, lint, your policies and security rules, a cost estimate, and the project budget. Failures go back to the agent to fix. Applies to production then run through ops0's governed deployments, with plan review, policy checks, and an approval step where the person who starts a deployment cannot approve it when separation of duties is on.
Does prevent_destroy stop terraform destroy?
Yes, for the resources that carry it. Terraform rejects any plan that would destroy a resource with prevent_destroy = true, including the plan terraform destroy makes. It stops protecting the resource as soon as the setting or the resource block is removed from the configuration.
How do I stop an AI agent from running terraform destroy?
Use a pre-command hook, not an instruction. Coding agents that support hooks run a script before each shell command; the script matches destroy commands and refuses them before they start. Back it with prevent_destroy on stateful resources, deletion protection in the cloud, and credentials that cannot delete production.
Should AI coding agents be allowed to run terraform apply?
Not against production. A common split is that the agent writes and validates code, and a pipeline runs plan and apply with policy checks and a human approval, so every production change has a reviewed plan and an approver on record.
What happens to RDS snapshots when an instance is deleted by Terraform?
In the AWS provider, delete_automated_backups defaults to true, so automated backups are removed with the instance. Set it to false, keep skip_final_snapshot = false with a final_snapshot_identifier, and keep backups outside the stack so a destroy leaves something to restore.
Can I still tear down a sandbox if destroy is blocked?
Yes. With the ops0 CLI, a person sets OPS0_ALLOW_DESTROY=1 in the environment before starting the session. An agent that adds the variable to its own command is still blocked, because the hook checks its own environment.
- How I Dropped Our Production Database and Now Pay 10% More for AWS: Alexey Grigorev. The February 2026 incident and its timeline.
- The lifecycle meta-argument: HashiCorp. How prevent_destroy works and its limits.
- aws_db_instance: Terraform Registry. deletion_protection, skip_final_snapshot, and delete_automated_backups.
- Deleting a DB instance: AWS. RDS deletion protection.
- S3 backend: HashiCorp. Remote state with locking.
- ops0 CLI: ops0 on GitHub. The pre-command hook and end-of-turn checks.