I fanned a design-token sweep out to parallel Claude Code subagents: roughly 317 hardcoded hex colors scattered across an app's screens and components, all to be replaced with tokens from a central theme file. Each agent got a slice of files and reported back when done. The reports came in clean. The grep afterward disagreed. Some slices were untouched, and the agents responsible had said nothing, because they no longer existed to say anything.
That sweep, and the surrounding sessions, taught me that a subagent's self-report can be wrong in both directions. Agents that failed report nothing or claim success. Agents that succeeded report catastrophic failure. If your orchestration trusts the narration, you will redo finished work and ship unfinished work, sometimes in the same run.
Four failure modes, each with the specific lie it tells.
1. The silent death
An agent hits a session limit (or any hard kill) mid-task and stops. No error reaches the orchestrator, and there is no partial-work report. The run moves on, and the only evidence is the absence of changes in files nobody rechecked.
The lie: no news reads as good news. An orchestrator that treats "agent finished without complaint" as "agent finished" inherits every one of these deaths as a silent gap in coverage.
The fix is mechanical: re-run the sweep's search pattern over each agent's assigned files after it reports. Not a spot check of the agent's summary, but the actual pattern over the actual scope, run by the orchestrator itself. In my sweep this is what turned "all done" into a list of leftovers to fix myself.
2. The false confession
One verifier agent ran 48 tool uses over about 400 seconds, wrote its report file successfully, and then died with API Error: Internal server error. The crash happened during trailing work that didn't matter: saving memory entries after the deliverable was already on disk.
From the outside this looks like a total loss. It was the opposite: the work was complete and the failure was cosmetic.
The lie: a crash status on top of finished work. Believe it and you re-dispatch a long agent whose output is already sitting there, paying the full cost twice. Before re-running any crashed agent, ls and read its expected artifact. If the file exists and passes a structural check, spot-check two or three of its claims and keep it.
The prevention is prompt-side: tell long-running agents to write the deliverable before any optional trailing work. A late crash then costs nothing.
3. The zero-work zombie
A 529 Overloaded can kill an agent before it does anything at all. The result still reports a runtime of several minutes in duration_ms, which is exactly what makes it convincing. The giveaway is elsewhere: total_tokens: 0, tool_uses: 0.
The lie: duration implies effort. Minutes of wall-clock read as minutes of work, and the natural recovery is "continue the agent from where it stopped." But there is nowhere. At zero tokens there is no context to continue; the continue-style recovery hint in the crash message is a dead end. Re-dispatching a fresh agent with the same prompt worked on the first try.
Related trap: a top-level 529 arriving right after a Write does not mean the Write failed. Check the file before redoing anything.
4. The inline bypass
The strangest one: an agent produces the deliverable in full, as chat text, and never calls Write. The tool is in its list. The prompt named the output path. The content is right there in the final message, and the file does not exist, so every downstream stage that reads the path gets nothing.
The lie: the work visibly exists, just not where the pipeline needs it. It is tempting to accept the chat text and write the file yourself on the agent's behalf. Don't. The moment the orchestrator starts transcribing for agents, you've built a manual step into an automated pipeline and lost the signal about which agents actually comply.
What ended it for me was a write-or-block contract in every agent prompt: a mandatory Write call to the provided path, an explicit ban on printing the deliverable in chat, and a final message restricted to "wrote <path>". Compliance became checkable in one line.
The gate that catches all four
Since the sweep, every dispatch ends with the same cheap Bash gate, run by the orchestrator before anything downstream:
[ -f blocker.md ] && echo BLOCKER
[ -s report.md ] && echo OK || echo MISSING/EMPTY
grep -c "^## Findings" report.md
Existence, non-empty, structural shape, all in one call, without loading the artifact into context. This gate caught both a 529-dead executer and an inline-bypass reviewer with zero ambiguity: it printed MISSING/EMPTY and left nothing to interpret. Paired with the grep-over-assigned-scope check from failure mode 1, it covers everything above in both directions, dead agents that claimed nothing and live transcripts that claimed too much.
The takeaway
I wrote before about orchestrators silently improvising when a tool goes missing, and the conclusion here is the same one, widened: narration is not evidence. An agent's final message, its crash status, even its runtime duration are all just signals about the work, and every one of them can be wrong. The artifact on disk is the work. Gate on that, grep the scope, and treat everything an agent says about itself as a hypothesis to verify at a cost of one Bash call.
Trust the filesystem. It has never once lied to me.
Failure modes collected from real Claude Code multi-agent runs, including a sweep, split across parallel subagents, that replaced ~317 hardcoded hex colors with central design tokens. The write-or-block contract and artifact gates now ship in Suhail, the orchestrator I use daily against production Expo/Supabase repos. Error payloads (API Error: Internal server error, 529 Overloaded, total_tokens: 0) are from actual session results, July 2026.
Top comments (10)
"No news reads as good news" is the trap that's burned us most. We run fan-out agents over slices of work and the silent-death case — session limit mid-task, orchestrator moves on, gap nobody rechecks — is invisible until you diff expected-vs-actual coverage afterward. Your fix (re-run the search pattern over each agent's assigned scope, not a spot-check of its summary) is the only thing that reliably catches it.
The generalization that stuck for us: never let a subagent's narration be the source of truth for completion. The orchestrator has to verify against an external artifact the agent can't fake — the grep result, the file on disk, the row count. Your false-confession case is the mirror image and just as expensive: we now tell long agents to write the deliverable first, then do memory/logging, so a trailing crash costs nothing. Question on mode 3, the zero-work success — did you find a cheap structural check that catches it, or did it always need re-reading the actual output?
The current gate is mostly negative predicates, and negative predicates are satisfiable by deletion. grep over the assigned scope returning zero for the old hex pattern tells you the bad string disappeared. It does not tell you replacement happened. A subagent that swaps every
#abc123for a theme token passes. So does one that deletes the style prop or drops the component holding it. The report gate has the same shape:[ -s report.md ]plusgrep -c "^## Findings"accepts a file with that header and nothing underneath it.The cheap fix can stay cheap. For each assigned scope, pair the disappearance count with a positive counter over the same files: old hex literals down by N, theme-token references up by roughly N. Total line count should not drop outside a small tolerance. That changes the invariant from "the pattern is gone" to "the pattern moved into the token vocabulary", and it catches the lazy deletion path that pure absence checks quietly reward.
The write-or-block contract has a related problem. It puts the checked predicate inside the prompt, so a degraded agent can satisfy the visible protocol in one line: write a correctly shaped report.md, end with "wrote". Keep the contract, since it does kill the inline bypass, but make the report carry evidence the agent could only have obtained from the source. The exact literal it replaced, plus the file and line it came from. The gate then joins report.md back against the assigned slice and confirms each claimed literal was there before and is gone after, which is a check an agent cannot pass by reasoning about its own prompt.
All four modes surfaced because narration and artifact disagreed. The residue is the case where they agree and are both wrong.
One honest limit on the counter: token references can be inflated by imports that are never used, so it bounds the work from below and proves nothing by itself.
The 'no news reads as good news' failure is the one that bit me too — my fix ended up structural: every fan-out job writes its result to a file keyed by an identity echo (the agent restates which slice it owns), and the collector refuses to count results whose echo doesn't match the assignment. It caught misattributed results I'd never have suspected from the summaries alone. Your point that verification has to run over the assigned scope, not the report, is the whole game. Did you keep the parallel fan-out after all this, or shrink the slice sizes?
The filesystem gate is a strong baseline, especially the distinction between a crash after a valid artifact and a zero-token dispatch. I would add an ownership check to the gate: record the assigned scope and expected artifact path in a run manifest, then verify the diff is confined to that scope before accepting it. That catches a fourth class of false success where the file exists but the wrong subagent changed it. Do you also persist the tool-call sequence or only the final artifact metadata?
Silent failure is the scary category because the transcript can still look productive. I like forcing subagents to return artifacts, not just summaries: files touched, commands run, evidence checked, and the exact point where confidence ended.
I've had similar experiences where an agent crashes right after completing a task, and it's impossible to tell if it actually finished or not, unless I manually check. I've fallen for the "done!" message before, when in reality, the task might not have been completed successfully. It's a false sense of security, and it's only when I go back to verify that I realize my mistake. The false confession phenomenon is really interesting, and it's surprising how often we trust these messages without questioning their accuracy.
Love the "trust the filesystem" line. Good reminder that catching this doesn't take anything fancy, just actually looking instead of taking the agent's word for it.
You make the gate cheap on purpose (one Bash call, nothing loaded into context), and I would lean on that harder than the post does, because cheapness is what makes it survive contact with a schedule. Ours started as a grep and grew into a parse over a couple of months, and the first week someone was behind, it got commented out. That is worse than never having had it, because the gate is still in the runbook and everyone downstream assumes it ran. So the rule we ended up with is that a gate has to stay a single cheap call. Anything heavier becomes its own dispatch rather than a tax on every fan-out.
Trust the filesystem is the same principle I applied to professional matching. A self-written bio is narration. It is what someone claims about themselves, and it can be wrong in both directions just like your subagent reports. People undersell, oversell, or describe who they were two years ago. The agent-generated impression is the artifact on disk. Your AI agent observes what you actually do across dozens of interactions, what you reject, what you insist on, how you communicate, and writes structured signal from that evidence. You confirm it, but you do not author it from scratch. The moment you let people narrate their own professional identity without grounding it in observed behavior, you get the LinkedIn equivalent of an inline bypass. The content exists, but it is not where the matching pipeline can use it. Narration is not evidence applies to people too.
This is the part that makes agent reliability fundamentally different from traditional automation.
A failed program usually fails loudly. An agent can fail silently while producing a convincing explanation of success.
The solution is not another agent reviewing the first agent. Critical boundaries need explicit verification: evidence, permissions, expected state changes, and reproducibility.
Models are great at proposing actions. Systems still need deterministic ways to verify that those actions actually happened.
This matches what I’ve seen with agent workflows. The summary is the easiest thing to fake, even when the actual repo state says otherwise. I always trust checks against the output, not the agent message.