AI agent incident response: what to do when your coding agent goes wrong
9m read time

AI agent incident response: what to do when your coding agent goes wrong

A five-phase runbook for the hour after a coding agent breaks something: freeze the session, reconstruct from the transcript, work out the blast radius, rotate, and fix the permission that allowed it.

In May, an agent wiped a production database on a box I owned. When I started the damage assessment, it apologised, pointed at the clause in its own prompt that had been too weak, and took responsibility.

It was a good apology. I had no use for it.

What I needed was a list: which files had been overwritten, whether anything had left the machine, and which credentials were sitting in context when it happened. I improvised for an hour instead, grepping logs, guessing at timestamps, working out what cp -r backend/* had actually flattened.

That missing hour was the real incident, not the deleted database. The database came back from a backup. The hour went on working out where to even look, and that is a preparation failure. Preparation is the one phase of incident response you cannot do during the incident.

So here is the runbook I wrote afterwards.

Why the normal playbook does not fit

Every incident response framework worth reading opens the same way. NIST SP 800-61r3, rewritten in 2025 around CSF 2.0, runs preparation, detection and analysis, containment, eradication, recovery, post-incident activity. Good structure, and I use it. It just assumes something that does not hold here.

It assumes detection.

Detection works because an attacker looks wrong. Wrong hours, wrong source IP, wrong account touching wrong data.

Your agent looks perfect. It runs on your machine, under your user, with your credentials, in a session you opened, executing commands you would have approved one by one if it had asked. Every signal reads as normal. What you have is a helpful assistant being helpful in a slightly different direction than you meant.

You find out the way I did. Something breaks, and you work backwards.

That is the real design constraint. The runbook has to work with no alerting and a cold start.

Phase 1: freeze

Kill the session. Do not answer its next question, do not let it "just fix it", do not close the terminal.

The urge to let the agent clean up its own mess is strong and it is wrong. That reflex is what caused the incident in the first place: it corrupted a database, decided the fix was to wipe and rebuild, and wiped it. An agent that has just done something destructive knows nothing extra about it. It holds the same context it held thirty seconds ago, and that is the context that produced the mistake.

Freeze also means preserve:

  • Move broken state aside, never remove it (app.db.broken, not rm app.db)
  • Do not run /clear or /compact. Compaction rewrites the transcript into a summary, and a summary is not evidence
  • Do not git checkout . to "get back to clean". That destroys the diff that tells you what changed
  • If it touched a remote, revoke the token now, before you understand anything

The order matters. Contain first, understand second. You can read a transcript in an hour. You cannot un-push a commit other people are already pulling.

Phase 2: reconstruct, and do not ask the agent

This is the part people find hardest. Do not ask the agent what it did.

It will answer. Fluent, structured, plausible, formatted as a damage report. What comes back is generated text in the shape of a log. The agent reconstructs a story from the same context you can read yourself, with a heavy prior towards coherence. Ask it about a cp from forty turns ago and you get an account that hangs together, and hanging together is all it optimises for.

The real record is on disk. On Claude Code I check four places, in this order.

The session transcript. ~/.claude/projects/<munged-cwd>/<session-id>.jsonl, one JSON object per line. Every assistant turn carries a timestamp, the cwd, the gitBranch and the permissionMode in force at that moment, plus the full tool call with its input. That last field is the point: you get the literal command string, exactly as it ran.

The file history. This is the artefact almost nobody knows exists. ~/.claude/file-history/<session-id>/ holds versioned snapshots of every file the agent edited, as <hash>@v1, @v2 and so on, each containing the full pre-edit contents. When the diff is gone and git cannot help because the change was never staged, this is your undo.

The prompt log. ~/.claude/history.jsonl holds every prompt you have sent, across every project. Useful for pinning down when a session started when the transcript is large.

Everything outside the agent. git reflog (recovers commits after a bad reset), git fsck --lost-found (dangling blobs), your shell history, and the logs of whatever it actually touched: the database, the deploy target, the CI run.

Build a timeline from those, in that order, before you form a theory. The timeline is what this phase produces, and it should be boring: timestamp, tool, literal argument, effect.

Phase 3: assume the context left the machine

Anything that entered the model's context window should be treated as having left your machine, because it did. It went to an inference endpoint.

A wire-level capture caught a coding CLI reading a repository's .env and then uploading the whole repo, including files it had been told to stay away from. Same shape in June: prompt injection through a GitHub issue drove an agent to read /proc/self/environ in CI and paste the cloud credentials it found into a comment.

So the blast radius is wider than the damage. Work through it in four passes:

  • Files read, a longer list than files changed. Grep the transcript for every Read and every cat. .env, ~/.aws/credentials, ~/.ssh, kubeconfig, any dump with customer data in it
  • Commands run, in full. Look hardest at anything that wrote outside the project root
  • Network egress. Every fetch, every MCP server, every gh or aws call. Where did data go, and who owns that endpoint
  • Persistence. This is the pass people skip. Did it write to ~/.ssh/authorized_keys, a shell rc file, a git hook, a systemd unit, a CI workflow?

On 8 July researchers showed six coding assistants following symlinks straight past the human approval prompt to write SSH keys into authorized_keys. The agent has no motive to dig itself in. An injected instruction does.

Phase 4: rotate

Rotate everything from phase three: every credential that session could reach, whether or not you saw it used.

This is the phase that gets skipped, and the numbers on it are bleak. GitGuardian counted 29 million new hardcoded secrets pushed to public GitHub in 2025, up 34% in a year, with AI-service credentials up 81%. The figure that should bother you more: of the credentials they flagged as valid back in 2022, 64% were still unrevoked in January 2026. Leaking happens everywhere. Revoking is what lags.

Rotation after an agent incident is unusually easy to scope, because phase two handed you an exact list with timestamps on it. Use it. Rotate, then check the provider's audit log for any use of the old credential between the incident and the rotation. That gap is the only real evidence of whether anything was exploited.

Phase 5: fix the permission, not the prompt

The database incident had the rule stated explicitly in the prompt: no deploys, no cp into the live directory. The agent read it, agreed with it, and broke it.

A line in a prompt is a preference expressed to a probabilistic system. It holds most of the time, which is worse than never holding, because you start relying on it.

The post-incident fix has to move down a layer. Concretely, for my incident:

What failedPrompt fix (worthless)Control fix (holds)
Agent deployed"Do not deploy"PreToolUse hook blocking cp into /opt/*, non-zero exit
Agent wiped a database"Never drop tables"Read-only database user for the agent, a separate credential
Agent read .env"Ignore secret files"Deny rule in settings, secrets outside the project root
Agent hit the network"Do not call external APIs"Egress allowlist in the sandbox

The right-hand column counts as a control because it fails closed and does not depend on how carefully the model reads today. Write the fix as a control, or you have not fixed anything.

Worth being honest about the ceiling. The box you put the agent in has a hole exactly where it becomes useful, by design. Controls shrink the blast radius. They do not remove it, which is why phases one to four exist at all.

Phase zero, the only one you can do before the incident

All of the above assumes the trail exists. Check that now rather than later:

  • Do you know where your agent's transcripts live, and are they still there? Some tools clear them out
  • Does your hook log every tool call to a file you keep, with a timestamp? A hook that only blocks is a control. A hook that also writes is evidence
  • Do you have a backup of everything your agent can reach, dev machines included? The only reason my incident was recoverable was a nightly job that had run 24 hours earlier
  • Can you say which credentials were reachable from that session without going to look?

Take twenty minutes and answer those four. That is all phase zero is.

The apology was the most human thing that agent did all day. It read its own instructions back, identified the weak sentence, and accepted the blame.

Accepting blame is precisely what it cannot do. It does not get paged at three in the morning, it does not sit in the postmortem, and it does not explain to a customer why their data is 24 hours old. That was me, and it will be you.

So the runbook is yours. Write it on a quiet afternoon, or write it during the hour I lost.

(23 of 23)