“No model reviews its own homework” did not start as a slogan. It started as the only honest description of four consecutive review cycles where the fix for a real bug quietly shipped a new one. The new one was caught, every time, by a reviewer who had not written the fix.
Cycle one: the pipeline fix that still leaked a raw secret
VeilGremlin’s masking pipeline composes six independently-built crates into two functions, scan and mask, with one job: no raw detected value reaches the output text, the vault, or the audit trail in violation of policy. A fresh-context reviewer, given only the artifact and the contract and explicitly forbidden from reading the author’s own design notes, returned thirteen findings. Two were High, and both were real. The sharper one: the code that resolved overlapping detector findings picked a winner and dropped the loser whole, uncovered bytes included; the entropy detector’s own documented tokenizer residual meant a secret finding could be partially overlapped by an accepted, lower-priority match, and the losing finding’s uncovered head bytes were dropped along with it, unmasked, appearing raw in the output text: a live secret leak through the exact pipeline built to stop one. The other: a literal .env file was not blocked, because the language’s own path-extension function returns nothing for a dotfile, and the contract’s canonical blocking example fell through to pass-by-default; the original test’s filename happened to carry a real extension and hid the gap.
Both were fixed, the losing finding trimmed to its genuinely uncovered fragment instead of dropped, the dotfile matcher taught to check the first segment after the leading dot. A second, cross-model round then pointed itself at exactly the surfaces the first round’s fixes had just created, and found one more real bug living inside the fix: the audit trail’s detection counts were now being tallied after overlap trimming, so one raw finding split into fragments by the trimming logic itself got counted as multiple detections, corrupting the exact metric the fix existed to make trustworthy. Counting moved to the raw, pre-resolution findings instead.
Cycle two: the exit code that meant the opposite of what it said
VeilGremlin’s hook adapter had a frozen contract: exit 0 for pass, exit 2 for a transformed payload, exit 1 to block. It shipped, it passed its own tests, and it did not work, because Claude Code’s actual hook semantics only treat exit 2 as blocking; every other non-zero exit, including the adapter’s own “1 = block,” is a non-blocking warning after which the raw content continues. Every fail-closed path in the design (an unparseable payload, a masking error, a policy block) failed open in the only consumer that mattered. A fresh-context reviewer, given the contract and the diff but told to distrust the spec rather than assume it, checked the claim against the platform’s own hooks documentation instead of taking the code’s comments at face value. That single verification found what eighteen findings’ worth of careful code had missed: the contract itself was wrong, not just its implementation.
The fix reassigned every outcome to real exit codes and shipped. A second, cross-model round then found the fix’s own gap: it made every path inside the hook function fail closed, but an error that bubbled out (a bad state directory, a failed keychain, a malformed policy file) hit the generic error handler and exited 1, the non-blocking code, again. The fourth instance in this one project of a fix leaving its own seam. The wrapper now exits 2 for any error on the hook path, no exceptions, and a regression test locks it there.
Cycle three: the benchmark that lied in both directions
The eval harness for the same product returned an honest-looking verdict (NO-GO), and it was right for the wrong reasons. The placeholder-consistency probe joined its two test values with a pipe character, which is legal in an email address; the detector matched straight through the separator, split one probe into two different values, and manufactured a 66.7% “instability” reading that would have been a permanent false failure. Meanwhile two passing gates were weaker than they looked: one measurement never checked that the thing it was timing actually succeeded, so a future argument rename would have produced a glowing, meaningless sub-millisecond result; a structural-guards gate asserted a code path fired without ever measuring that it actually had, so it would pass even if the underlying mechanism silently died.
A fresh-context pass fixed all of it: a newline instead of a pipe, exit-code checks on every timed call, real measurement instead of assertion. A second, cross-model round then found three more vacuous-pass holes inside that very fix: per-detector false-positive rates that could be diluted by unrelated true positives elsewhere in the corpus; an empty test slice that would silently read as a perfect score instead of an error; a stability metric still counting binding-less, all-identical redaction markers as if they were real matched pairs. Post-fix, the verdict was still NO-GO, now for exactly one honest reason, with the false ones closed.
Cycle four: the fix that broke its own audit trail
The hook adapter’s hard-deny fix from cycle two had one more life in it. Denying a destination before the pipeline’s engine opens is the safer order: nothing gets a chance to run against a system that should never have been reached at all. But the pipeline’s demask command shared its early-return path between the hard-deny check and the call that logs the denial, so the same fix that closed the ordering gap also made the one denial the command can produce silently un-audited: refused correctly, recorded nowhere. Nobody caught it by re-reading the fix. It surfaced because a fresh-context reviewer ran the product’s own dogfood demo plan end to end and noticed that a step reading “the denial appears in the audit log” had quietly become impossible to satisfy. The fix was narrowed so both properties hold at once: the engine still opens before anything can run when it can, the denial still flows through and gets audited when it does, and a destination that can never be reached stays refused, unaudited, and explicitly labeled that way on the terminal rather than raised as an error.
The technique the whole pattern implies
A month later, a much smaller change proved the same lesson at a different scale. A single-model pass over a session-proxy daemon found and fixed seven real issues, including a documented, deliberately-deferred gap: the primitive that would let a future caller safely release a stale mapping when a session ends. A cross-model round, asked to review that fix specifically, found the sharpest bug either round produced: the new release primitive itself had a cross-session correctness hole. It removed a mapping by address alone, so a delayed cleanup call from an old session could delete a different, currently-live session’s mapping the instant a second session reused the same port. The fix that was supposed to close a documented risk opened a sharper version of the same risk, inside its own new code.
Why it became doctrine, not a preference
None of these four catches came from re-reading the original work more carefully. Every one came from a reviewer with no investment in the fix being right, fresh context in three cases, a different model family in most of them, and in every case explicit instruction to verify the claim against the real artifact (the platform docs, the running harness, the actual code) rather than trust the comments describing it. That is now standing practice: one model builds, a fresh-context or cross-model pass doubts, and a third pass, when the stakes justify it, cross-checks again. Not because any one model is unreliable, but because a fix’s blind spot is, by construction, invisible to the model that just wrote it.
The part worth stealing
If your review process re-reads the whole file every time, you are spending review budget on code that already survived one pass and skipping the part most likely to be wrong: whatever just changed. Point your next review specifically at the fix, not the file, and give the reviewer a reason to distrust the fix’s own claims about itself.
What Next
The Fixes That Held Changed a Rule
Three adversarial review rounds on the same codebase, and the distinction that decided which fixes actually lasted: the ones that changed a rule held, the ones that changed a line came back.