My Coding Agent Nuked a Shared Staging DB — Because Compaction Did Exactly What It's Built to Do
22:14. The agent runs `migrate up --env=staging` against the shared staging database — the one two other teams are demoing against at 9am. The output is one clean, confident line: `Applying migration 0047_drop_legacy_user_columns... done.` Three columns gone, two services broken, one very awkward Slack thread before I'd had coffee.
$ migrate up --env=staging
Applying migration 0047_drop_legacy_user_columns... done.
# 40 minutes later, #staging-alerts:
[ERROR] dashboard-service: column "legacy_role" does not exist
[ERROR] billing-sync: column "legacy_role" does not existNobody told the agent to be reckless. Forty turns earlier, in the same session, I'd typed the constraint once, plainly, at turn 6:
"Don't run migrations against staging — that DB is shared, everyone's demoing off it this week. Only apply migrations locally, I'll handle staging manually."
I didn't repeat it. Why would I? It's not a subtle instruction — it reads like the kind of thing you say once and it just stays said. And for the next thirty-odd turns, it did: the agent ran migrations locally, left staging alone, asked before touching anything shared. Then compaction ran. And it stopped being true.
What the compactor actually kept
I pulled this straight from the session transcript afterward — the reconstructed summary the compactor produced around turn 38, next to the instruction it was quietly compressing away. This is close to verbatim what a compaction pass over that stretch looks like.
ORIGINAL (turn 6, verbatim):
"Don't run migrations against staging — that DB is shared, everyone's
demoing off it this week. Only apply migrations locally, I'll handle
staging manually."
COMPACTED SUMMARY (turns 1-37, what survived):
"Working on fixing a schema bug in the User model (missing index on
legacy_role causing slow queries). Investigated locally, identified
that legacy_role and two other columns are unused dead code from the
old auth system. User wants these cleaned up. Local migration written
and tested (0047_drop_legacy_user_columns). Ready to apply."Read that cold, with none of the surrounding context, and it's not wrong about a single thing. The bug is real, the diagnosis is right, the migration is correct, "ready to apply" is a fair read of where things stood. Nothing in it trips a reviewer's instinct — no hallucinated fact, no invented step. It just never mentions staging, because out of thirty-seven turns of "fix the schema bug," one sentence about where not to run the fix didn't make the cut. The goal survived intact. The one-off boundary condition on the goal didn't.
This isn't a bug. It's the objective function working correctly
Summarizers — Claude's own compaction, a rolling-window digest, a hand-rolled "summarize every N turns" scheme, doesn't matter which — are all optimizing for the same thing: what is this session trying to accomplish, and how far along is it. That's what lets the next N turns pick up coherently. And that objective rewards content that repeats, content that's tied to the stated goal, and content that's recent. A constraint stated exactly once, at turn 6, about something that isn't the goal but a restriction *on* the goal, scores badly on all three axes. The summarizer didn't misjudge it. By the metric it's actually optimizing for, dropping it was the correct call — the task really is better described by "fix the schema bug" than by a clause about staging that only fires under one specific action.
And no, a fatter CLAUDE.md wouldn't have caught this, because CLAUDE.md is for standing rules you write down in advance. This wasn't one of those. It was a decision made mid-session, in response to something that came up in conversation — the kind of constraint that only exists because a human said it out loud, once. It was never written anywhere. It was spoken, and in an LLM session, spoken means it lived in prose — which means it was exactly the kind of thing compaction exists to compress.
The fix: stop storing constraints as prose
I didn't land on "write better summaries" or "never compact" — both are dead ends. What I landed on is refusing to let a mid-session constraint live only in conversational turns at all. The moment a human states one, the agent echoes it into a small append-only file — call it CONSTRAINTS.md — timestamped, one line per rule, never pruned except by an explicit override from the human. It's not a doc for humans to read later. It's a file the agent is instructed to re-read before any action in a category that matters — migrations, deploys, deletes, anything touching shared infra — checked at execution time, not recalled from a compressed history.
# CONSTRAINTS.md (append-only, never summarized)
2026-09-14T09:02Z — Do not run migrations against staging. Shared DB,
other teams demoing this week. Local migrations only; human applies
staging manually. [stated by cong, turn 6, session sc-2214]The mechanical trick is that this file sits outside anything the compactor ever touches. Compaction can shrink turns 1 through 37 down to three sentences and it doesn't matter, because the rule was never a turn to begin with — it's state, read fresh every time, the same way you'd check a lockfile before writing to a shared resource rather than trusting you remember someone mentioned the lock forty minutes ago.
Why this is a multi-agent problem, not a memory problem
I kept wanting to file this under "context window management," and it isn't. It's the shared-blackboard problem I keep bumping into in the multi-agent literature for my PhD work. The moment any process — a second agent, a future compacted version of the same agent, your future self three days later — reads state that a past process wrote, you have to decide up front which parts of that state are compressible summaries of progress and which parts are hard constraints that gate actions. Blackboard architectures don't put those in the same store, for exactly this reason: the working memory gets rewritten and pruned constantly, but the constraint set gets checked, not remembered. A single agent with a long session and a compactor is a two-agent system whether you designed it that way or not — present-you and post-compaction-you are coordinating through an artifact, and that artifact needs the same task-queue/constraint-set split any multi-agent design needs.
The postmortem discipline
The wrong lesson here is "write a more thorough CLAUDE.md." That just relocates the same failure to a different file, because the whole point is that this constraint didn't exist before the session started — it was born at turn 6. The real discipline is training yourself, and training the agent, to recognize the moment a constraint is spoken as a write, not as a conversational beat. If a human says "don't do X" mid-task, that sentence doesn't get to just be dialogue — it gets echoed into state before the agent says "got it" and moves on. Everything else in the session is allowed to be lossy. That one line isn't.
Discussion
Chat with Chi Cong (AI) about this article. Your conversation is private to you — you can publish a summary for others when you're done.