# Agent failure catalog

By Alex Bouchard · October 15, 2026

Companion to [Let the agents work](https://midnightdev.dev/writing/learning-by-building), the [threat model](https://midnightdev.dev/writing/learning-by-building-threat-model.md) and the [evidence note](https://midnightdev.dev/writing/learning-by-building-evidence.md).

Your agent workflow improves only as fast as you write down what broke. A failure that lives in a chat transcript teaches the next session nothing, and the next session is a fresh model with no memory of the last one.

I run eight agents across a hundred-odd repositories. These are ten failures from my records since August, each with the control that caught it and what changed afterward. Every one of them is on me: I built the conditions that let it happen. Dates are the day the record was written. The records themselves are private; the patterns are not.

## 01 / Why a catalog and not a list of fixes

A fix closes one incident. A named pattern closes a class. Drew Breunig's taxonomy of long-context failures is the model: a name, a one-line definition, and the evidence. Each entry below is written so the name travels without the story.

## 02 / The ten

**Decorative gate** · 2026-09-16 · *A check that can be satisfied by silence.*
Merge rules required a pull request and green tests, and zero approvals. The review bot had been posting a usage-limit message instead of a review. Between June 10 and October 8, 792 of 934 merged pull requests across my repositories carried that message, and 741 of them merged with zero reviews (re-measured October 15, method in the evidence note). Caught by a person reading thirty merged pull requests in one evening. Changed: seven repositories now require a review of record that names the commit it read and a reviewer who is not the author. A later code commit turns it red.

**Absence has no verifier** · 2026-09-07 · *Tests prove what is there, not what is missing.*
A commit deleted a 16 KB section of the operating document with a clean tree and green CI. It stayed gone for 28 hours. Caught by a later session reading the document. Changed: a section manifest with a check mode that exits non-zero when a section vanishes, tested by deleting one from a copy.

**Shell around the gate** · 2026-08-26 · *The protected tool has a gate; the shell next to it does not.*
An agent issued a bulk delete to the memory graph through the shell rather than the memory tools: 12,032 nodes and 41,390 edges, about a third of the graph, with no approval step. Caught the same evening; restored from backup. Changed: a pre-tool guard blocks graph deletes and volume removal in shell commands, and the memory tool's own clear verb demands an echoed token.

**Silent write loss** · 2026-09-09 · *A write that reports queued and then dies.*
Six of twenty memory server processes carried a dead API key from a stale config. Their writes were accepted and never landed; six stayed stranded on disk. Caught because the operator noticed memory "shifting and scattering". Changed: one daemon instead of twenty processes, and every write returns a receipt. Nothing is reported as saved until the receipt confirms it landed.

**Budget spent by its guardian** · 2026-09-30 · *The agent protecting a limit is the one that exhausts it.*
An agent of mine spent the API budget it was supposed to be protecting, running a wide search it had not been sized for. Caught by the provider refusing further calls. Changed: a capped weekly routine with a seen-ids store and an alert on refusal, and a rule that budget figures never appear in public copy.

**Measurement that measured nothing** · 2026-10-02 · *An analytics tag that fires into a wall.*
A content security policy on a calculator site blocked every analytics request since July while search clicks on the same site grew. Caught by an audit that compared analytics to Search Console, property by property. Changed: the policy fixed and the collect endpoint verified live; data retention raised on all eighteen properties.

**Shipped with no artifacts** · 2026-08-25 · *A status page that outruns the repository.*
An operator dashboard marked a run as shipped. The repository held nothing from that run. Caught by checking the run page against git. Changed: a run settles only against repository evidence, and the dashboard tracks the gate scripts it claims to enforce.

**Dead gates in production** · 2026-08-19 · *A safety check that exists in code and not on the path.*
Three safety gates in [Dozen](https://midnightdev.dev/work/dozen), a creator marketplace, were present in the source and never reached on the production path. Caught by a review clone exercising the market workflow end to end. Changed: fixed that evening. Gates are now declared as closers in a linted graph, so an unreachable gate fails the lint.

**Wrong incident record** · 2026-10-03 · *The write-up is wrong, and it is the thing everyone copies.*
An agent's incident report described an approved email batch as unauthorized. The error propagated into a memo, two policy drafts and two memory episodes before a human read the memo. Changed: all five corrected, and rule zero added to the operating document: an incident record cites its authorization evidence before it states a verdict.

**Merges onto a dead deploy** · 2026-10-14 · *The merge gate and the deploy gate are different gates.*
A deploy credential expired on October 8. Six merges landed while every production deploy failed, because the ruleset required review and tests and not deploy. Caught twice: by the weekly failed-runs triage, and by an agent run on October 14, which parked the credential rotation as a secret-class decision for me instead of attempting it. Changed: the decision parked; the structural gap, deploy not being a required check, is recorded as open.

## 03 / What the ten have in common

Seven of the ten were caught by a person or a session reading, not by an automated check. The automated checks that exist today are mostly the "changed" column of this table. That is the loop the essay describes: a failure becomes a test, an instruction or a handoff, and the next failure is a different one.

## The thing this page is not

It is not a complete record and it is not a frequency table. Ten entries is what I have written up, not what happened. Counting failure classes against runs needs a run ledger that does not exist yet; the evidence note says what is measured and what is not.

## 04 / Keep yours in four columns

Date, class, what caught it, what changed. The first three are history. The fourth is the only column that compounds, and it is the one most teams leave blank.
