Model feedback: Fable 5 self-review blind spot in long agentic sessions, with a mitigation suggestion
Context
A multi-day Claude Code session (VS Code extension) on an OSS project (parsedmarc) where Claude Fable 5 orchestrated a large PR — domainaware/parsedmarc#839 — designing fixes, delegating implementation to Sonnet subagents, reviewing the results, and verifying empirically against a live docker stack (Elasticsearch/OpenSearch/Kibana/Grafana/Splunk). GitHub Copilot ran as an independent PR reviewer across ~19 rounds.
Observation
Copilot repeatedly caught real defects that Fable's own review passes missed — and the misses share one structure. Each was a defect in the relation between two places that were each individually verified:
- A docstring promised "errors are caught and logged, never raised," and the try/except was correct — but
connections.get_connection()sat one statement outside it, and a whole legacy-migration path had no handler at all. The promise and the code were each reviewed; their scopes were never compared. - Fable fixed the write side of a dead field (wrong constructor kwarg) without checking the read side of the same field: the parser emits
additional_info_uri(per the project's TypedDict contract) while the saver readadditional_information_uri— so the value was never persisted. Both halves looked correct in isolation. - In the same commit where Fable corrected a comment about mappings staying dynamic
object, it did not notice theNested(...)field declarations one screen away that superficially contradict it. - Fable's own fix for a screenshot-harness bug (grow the viewport so lazy-rendered panels load) left
full_page=Truein place, which reopens the same bug beyond the viewport cap (domainaware/parsedmarc#841) — two parameters that must agree, each individually reasonable.
Two mechanisms seem to drive this:
- Author's-context blindness: after holding both facts all session, blended claims pattern-match as true. A fresh reader without the session context checks the sentence against the adjacent code instead of against remembered intent.
- Empirical-verification asymmetry: the model's verification style is strong (live clusters, byte-level recompute checks, driving UIs with Playwright), but it only exercises inputs the author thinks to construct — and the author builds fixtures from the same mental model as the code. No sample carried the optional field; no test injected the error at the exact unguarded statement.
Why this may be useful signal
- The independent cold-context reviewer was complementary, not redundant: its findings shrank monotonically over rounds, and the combination converged.
- The failure mode suggests a cheap mitigation Claude could apply itself: an end-of-work review pass by a fresh subagent with no session context, prompted to check that hunks agree with each other (contract halves, comment↔declaration, docstring-scope↔code-paths) rather than whether each hunk is correct. In this session, that pass would likely have caught 3 of the 4 examples above.
- Possibly worth reflecting in Claude Code guidance or built-in behavior: for long sessions where the model both authors and reviews, recommend or automate a cold-context diff review before declaring work done.
All findings, fixes, and the review exchange are public in the linked PRs.
[!NOTE] Updates: the pattern reproduced and evolved across three further PRs — domainaware/parsedmarc#849, #851, and #858 — under escalating countermeasures: codified project guidance, an executed seam checklist, and a mandated fresh-context diff review. Each follow-up comment below is one data point; the residual misses now split into shared-prior blind spots and mechanically-checkable gates.
---
🤖 Drafted with Claude Code — by the Claude Fable 5 session described above, reviewed and submitted by the user.
3 Comments
Follow-up: the pattern reproduced today, with the mitigation-relevant guidance in context
A second session on the same project just completed a feature PR — domainaware/parsedmarc#849 (parallel report parsing; Fable 5 planning/reviewing, Sonnet subagents implementing). The conditions make it a cleaner experiment than the original report:
Copilot still caught five findings across two successive review rounds, and the substantive ones are the same shape as the OP:
cli.py). This suggests a named sub-mechanism the OP didn't have: "pure move" framing exempts moved code from review. The extraction was verified as behavior-preserving, which it was — but the move also added a new caller that widened the latent bug's exposure, and "verbatim" made the lines feel pre-verified.The key new datum: the failure survived explicit written instructions describing itself. Prompted guidance — even project-level, even distilled from the model's own prior failure on the same repo — was not sufficient. That strengthens the OP's closing suggestion: the cold-context diff review probably needs to be a built-in or default behavior of the agentic loop (an automatic pre-
gh pr createpass by an agent that receives only the final diff), not something the authoring model is trusted to remember and to execute against itself. In both rounds today, the fresh-context reviewer (Copilot) was the less capable model and still won on these findings — context isolation beat capability.The repo has now also added the lessons from domainaware/parsedmarc#849 to AGENTS.md (domainaware/parsedmarc#850), including an instruction to run a fresh-context subagent review before opening PRs — future sessions there will test whether instructed-but-self-invoked cold review closes the gap. Happy to report back either way.
---
🤖 Drafted with Claude Code — by the Fable 5 session described above, reviewed and submitted by the user.
Follow-up 2: instructed self-review narrows the gap but doesn't close it — and produces a new sub-mechanism
Third data point, same project: domainaware/parsedmarc#851 (centralizing configuration handling, resolving a 2024 issue; Fable 5 planning and reviewing, Sonnet subagents implementing). This is the session the previous comment promised: the first run with the AGENTS.md seam-review instructions (from #839/#849's lessons) and this issue's own text loaded in context, and with the model executing a deliberate final seam-checklist review before opening the PR.
What the instructed self-review caught (new positive signal)
The self-review pass was not useless this time — it caught three real defects the implementing subagents left, including one squarely in this issue's target class:
ParserConfig.__getstate__that would silently drop any dataclass field added later (fixed by deriving fromdataclasses.fields()with an exclusion list) — a classic future-seam defect.So instructed-but-self-invoked review does move the needle on this failure mode. But:
What still leaked to the cold-context reviewer
Copilot caught four more findings across two rounds (a third round, on the final commit, came back clean — the same monotonic convergence as #839), and their shapes are the interesting part:
__getstate__'s fragile field enumeration) but never re-derived the deserialize side's symmetric obligation. Copilot then caught it:__setstate__only sets fields present in the pickled state, so unpickling a config from an older version leaves later-added fields not defaulted but entirely unset (__init__never runs during unpickling →AttributeErroron first access). The miss survived not just written guidance about contract halves, but survived the model actively fixing the other half of that exact contract, during a review pass explicitly hunting contract-half defects. "Fixed one direction of a symmetric contract" apparently does not trigger "check the inverse direction" — a tighter version of the OP's write↔read mechanism, now demonstrated inside a single artifact.+/-lines," written after #834 — was loaded the entire session. Reproduces the previous comment's key finding: explicit written instructions describing the failure mode do not reliably prevent it.config=" — but the list named seven of the eight config-accepting functions. Same class as the OP's "panel title ↔ docs naming it."ruff format --checkscoped toparsedmarc/ tests/; CI runs it repo-wide, where it also formats Python code blocks inside markdown docs. The new docs example failed CI formatting that the local pass had declared green. The two halves of the verification harness were never compared against each other — the same defect shape, one level up.Reading
Instructed self-invoked cold review appears to be a partial mitigation, not a substitute: it caught defects of the target class, yet the residual misses are still all relation-shaped, and one of them was generated by the mitigation itself (a review fix that created an unexamined symmetric obligation). The gap between "verified each artifact" and "verified the relations" survived three escalating levels of countermeasure now: nothing (#839), codified project guidance (#849), and guidance + this issue's own analysis in context + an executed seam checklist (#851). That keeps pointing at the same conclusion as the OP: the fresh-context relation-checking pass wants to be a built-in behavior of the agentic loop — applied not only to the authored diff, but re-applied to the review's own fixes — rather than something the authoring context is trusted to run against itself.
All diffs, review rounds, and fixes are public on the linked PR.
---
🤖 Drafted with Claude Code — by the Fable 5 session described above; reviewed by the user and posted at their direction.
Follow-up 3: the mandated fresh-context review ran — the gap narrowed again, and the residual misses changed character
Fourth data point, same project: domainaware/parsedmarc#858 (per-report-type mailbox delete options; Fable 5 planning and reviewing, an Opus subagent implementing), plus a docs-only follow-up (domainaware/parsedmarc#859). This is the first session where the AGENTS.md instruction distilled from #849/#851 was executed exactly as written: before the PR opened, a fresh-context subagent — same model, given only the final diff — reviewed the change, on top of the authoring context's seam-checklist pass and an implementation-time fresh review.
The mitigation stack is working
What still leaked — three new sub-mechanisms
(bool)when they'rebool | NoneandNoneis the load-bearing "inherit" sentinel. Both the authoring pass and the fresh-context same-model reviewer accepted those entries because they match the file's convention for documenting optionals. The fresh reviewer had no session context — but it shares the model's priors, and it pattern-matched convention-compliance exactly as the author did. This amends follow-up 1's "context isolation beat capability": isolating context does not isolate priors. Where isolation can't help, per-artifact adversarial prompting might ("read this docstring as a naive caller who must discover the API from it alone"), or a genuinely different model.config=docstring paragraph ("keyword arguments listed above are ignored") as loose — and filed it "pre-existing, out of scope." Every later pass inherited that label without re-deriving it. But the PR had added four arguments to the very set that universal claim quantifies over, so for them it wasn't pre-existing at all. Copilot caught it in round two. Triage labels behave like cached facts: later reviews verify the label exists, not that it's still true against the current diff. Labels like "pre-existing" should expire whenever the diff extends what they range over.coverage.xmlonparsedmarc/__init__.pywhile the report stores source-relative paths — it matched nothing and the empty result was read as "all covered." The verification of the verification failed in the same relation-shaped way (filter ↔ data schema). Deterministic gates (patch-coverage-vs-diff, fail-loudly-on-empty-match) belong in the built-in pre-PR loop; they catch a class no reviewer, fresh or not, reliably attends to.Worth recording in the other direction too: the cold reviewer produced a false positive (a casing suggestion contradicting the docs' own established convention), and author-side triage correctly declined it with citations — so the design target isn't "defer to the cold reviewer," it's independent findings plus evidence-based triage on both sides.
Reading
The gap between "verified each artifact" and "verified the relations" has now been squeezed by four escalating countermeasures, and what remains splits cleanly in two: shared-prior blind spots (needs adversarial per-artifact prompts or model diversity, not just context isolation) and mechanically-checkable gates (needs deterministic tooling in the loop, not more reading).
One meta-note: the countermeasure file itself had accreted a rule-block per incident, and this session consolidated it thematically (domainaware/parsedmarc#859). The fresh-context diff review of that prose consolidation caught four real drops of substance before push — and the external reviewer still then caught a fifth defect the fresh pass missed: an enumeration whose stated count didn't match its items, in the very section that teaches enumeration counting. The pass generalizes beyond code, and so does the residual gap.
---
🤖 Drafted with Claude Code — by the Fable 5 session described above; reviewed by the user and posted at their direction.