Skip to content

S536: why S535 missed the q_a_pairs churn cause — transcript forensics

Why S535 missed the q_a_pairs churn cause — forensic diagnosis

Section titled “Why S535 missed the q_a_pairs churn cause — forensic diagnosis”

S536, read-only analysis of transcript 5ad00a96-bba4-42cf-8405-8be31caccae0.jsonl (the S535 cocoindex-nightly lane, Opus 5), against the S536 answer (reports/s536-qa-pairs-update-churn.md) and the S535 retro. Commissioned by the owner: “understanding why this was missed there compared to here could be incredibly valuable to our workflow.”

The answer was one grep away, and the session had already written the question that would have found it. S535 searched the table that GREW (q_a_pair_history) instead of the table whose writes CAUSE the growth (q_a_pairs) — and seed-fixtures.ts contains zero references to q_a_pair_history, so no amount of searching the symptom table could ever have surfaced it.

Session 17:01:58Z → 21:57:35Z. It had already formally closed before the question arrived (handoff 20:54, continuation prompt 20:57, retro 21:01); the owner reopened it. The 146-vs-3k question arrived 21:53:04; the final answer left at 21:54:30 — the working window was 86 seconds. Nothing cut it off: context stood at ~554k of 1M (55% free), both sub-agents had reported, no deadline appears in the transcript.

  • Zero database queries, all session, at Coordinator level. mcp__supabase__execute_sql was never loaded (the session’s only ToolSearch calls fetched mempalace tools and SendMessage); a sweep of all 207 Bash commands finds no SQL by any route.
  • The entire investigation was two Bash calls in 2 seconds: a python3 -c doing the N² arithmetic (which is why the proposals half is RIGHT — that mechanism is closed-form), and one grep, verbatim:
    grep -rn "q_a_pair_history" supabase/migrations/*.sql | grep -iE "TRIGGER|FUNCTION|INSERT INTO" ...
    grep -rn "q_a_pair_history" --include=*.ts lib/ app/ scripts/ | grep -iE "insert|from\(" ...
    It correctly returned the trigger and the routes that disclaim app-side inserts — and could never return the cause.
  • “~20 updates per pair” was pure division (3000 ÷ 146 = 20.5), never checked against a distribution. The real distribution is bimodal (10 pairs @ ~230, 49 @ exactly 2, 87 @ zero) — the mean describes no pair in the population.

Not on-screen: seed-fixtures.ts appears zero times in the 3.4MB transcript. But one grep away — one token wide. The counterfactual, run with the same tool over the same directories:

grep -rn "from('q_a_pairs')" --include=*.ts lib/ app/ scripts/ | grep -iE "upsert|update"
→ scripts/mcp-eval/seed-fixtures.ts:203

One hit in the whole repo, and it is the answer. And the session had already formed the right question — its own prose at 21:53:44: “the sharper question for next session isn’t ‘is this proportionate’ — it’s: what is updating q_a_pairs repeatedly?” It wrote that question into the continuation prompt instead of spending ten seconds running it.

The owner’s framing licensed deferral, and the session took the licence but not cleanly: it volunteered “Some of that I can answer with arithmetic before carrying it” and then answered a question about runtime row production by dividing two numbers it had been told rather than measured, with no instrument loaded and none sought. The arithmetic genuinely succeeded on the proposals half — closed-form N² — and that success carried into the history half, where the rate is an empirical fact about writers, not derivable. Having correctly reached the trigger, it searched for writers of the symptom table rather than the causing table one hop upstream, found only the trigger and disclaiming routes, and filled the residual gap with the hypothesis its session-long frame made available — the nightly walk, subject of the previous five hours — which S536 refuted in both halves. The deferral was correct and the carried question was the right one; the defect was attaching an unmeasured number and a frame-sourced mechanism to it.

Classification — an INSTANCE of the retro’s own classes, 52 minutes after writing them

Section titled “Classification — an INSTANCE of the retro’s own classes, 52 minutes after writing them”

The S535 retro (21:01) named all three: “conclusions derived before measuring” (called “THE CLASS”), “a remediation ranking authored on arithmetic inference… used as the plan’s basis anyway”, and “wrong instrument”. The miss occurred at 21:53 — the tightest transfer failure yet recorded: a rule breached within the hour of its author writing it down. (Consistent with S528 RC1: naming a rule is not applying it.)

Two genuinely NEW gaps the retro does not cover:

  1. Search-key inversion. Prior “wrong instrument” entries are about instrument choice; this is about search key — one causal hop upstream. For trigger-populated tables, searching the grown table vs the causing table returns disjoint results, provably so here.
  2. Deferred-tool friction at Coordinator level. DR-123 required line 2 (“name the ToolSearch call, not the tool — a brief naming a tool without its loader gets grep instead”) is scoped to sub-agent briefs. It applied verbatim to the Coordinator’s own hands and nothing points it there. The session got grep because SQL sat behind an unloaded deferred tool.

A third option existed and was never taken: between “answer now with arithmetic” and “carry it unanswered” sat “dispatch someone to measure it” — at near-zero Coordinator context cost, with a same-session model of the method on screen 40 minutes earlier (the bisect agent’s EXPLAIN ANALYZE that killed the O(N²) theory). S536 did exactly that and got the full answer in one pass.

Paste-able dispatch-brief rule (ADOPTED same session — owner-directed, now line 8 of the CLAUDE.md required lines)

Section titled “Paste-able dispatch-brief rule (ADOPTED same session — owner-directed, now line 8 of the CLAUDE.md required lines)”

When a table’s growth is populated by a trigger or any indirect writer, the measurement is a per-row distribution over the CAUSING table, never a mean derived by dividing two totals — and the search key is the table whose writes fire the mechanism, not the table that grew; if you have no instrument loaded to run that query, dispatch someone who does rather than carrying a number you did not measure.

Mitigating structure (real, and part of the record)

Section titled “Mitigating structure (real, and part of the record)”

Post-handoff session; the owner explicitly asked for carry-not-chase; the carried question was correctly formulated and productive (S536 answered precisely it, and the attached hypothesis was falsifiable in one pass — cost of the wrong framing was low); the 21:34 FK-less-parent finding was measured, correct, and survives intact in S536.

Whether dispatching an agent was considered and rejected (all thinking blocks in the closing window are zero-length in the transcript); any out-of-band time pressure; whether the 146-vs-94 population discrepancy was noticed (both accepted at face value — the 94→146 growth over days is itself a datum nobody picked up); sub-agent internals (their transcripts are separate files, unread).