Measured re-check · Jacks-iMac

Reharm architecture: what the fresh local check found

The September 10 Claude Code audit asked for one thing before anyone acted on it, which was a fresh local check of its own configuration claims. This is that check, run on 2026-10-17 against the machine the audit describes.

hostname Jacks-iMac · 621 transcripts scanned · 24 project directories · 47 hook entries at 13:0x, 46 after

Contents
  1. The four verdicts
  2. The named hook scripts do not exist
  3. The baseline grew, and its biggest item was uncounted
  4. The engine is empty rather than half-built
  5. The session totals are not reproducible here
  6. The three-phase plan
  7. Provenance

The four verdicts

The diagnosis is right about the cost per turn and right about the engine being unfinished. Four of its specific, actionable claims do not survive the local check, and the largest single context item in the system is one it never counted.

Held

Per-turn context cost is real. Measured 625,521 cache-read tokens per assistant turn on September 10, against the audit's figure of roughly 556,000 for the Shiner session.

Confirmed by independent measurement of the transcript's own usage records. This is the claim that carries the remediation.

Wrong target

The three scripts the audit says to edit do not exist. run_gates.py, completion_stop_hook.py and no_stop_gate_hook.py return nothing from a find over all of ~/.claude.

The real Stop chain is 16 hooks under different names. Following the instruction verbatim would edit nothing and report success.

Worse than stated

The engine has zero bytes of executable Python. The audit describes a CLI that could import and inspect files with three commands missing. There is no CLI.

Eight empty __init__.py files, ten empty directories, and a SKILL.md that already marks the package not implemented.

Unverified

The headline totals are not reproducible on this Mac. Twelve concurrent sessions, 1,610 assistant turns, 496,941,520 cache-read tokens. September 10 here holds 1 session, 70 turns, 43,786,490 tokens.

Brandon runs two Macs. A MacBookPro scan is the one diagnostic this machine cannot run, so these four figures stay labelled unverified rather than refuted.

The named hook scripts do not exist

The audit attributes 204 of 251 blocked stopping attempts to three scripts and directs that two of them have their recursive stopping behaviour removed.

$ find /Users/drwu/.claude \( -name run_gates.py \
    -o -name completion_stop_hook.py -o -name no_stop_gate_hook.py \)
(no results)

What ~/.claude/settings.json actually wires is 46 hook entries across six events:

EventHooksReading
Stop1614 Python gates plus 2 shell one-liners, none scoped by matcher
PreToolUse13Matcher-scoped: Bash 9, Write|Edit|NotebookEdit 2, Agent|Task 1, AskUserQuestion 1
UserPromptSubmit97 on every user prompt, 2 wrapped in once_per_session.py
PostToolUse4Scoped to Write|Edit|MultiEdit|NotebookEdit
SessionStart3
Notification1

All 14 Python Stop gates can block. The audit directs edits to two scripts, so the retry-bound target covers fourteen files.

My first census was wrong, and it understated the problem

An earlier pass of this page said 11 of 14. That came from grepping for a "decision" block field and a literal exit(2), which misses the return 2 plus sys.exit(main()) form. Three gates block that way and were miscounted as silent: completion_stop_gate.py, chat_link_gate.py and chat_donelink_gate.py.

Direct proof rather than inference: five of these gates blocked this very session, including two of the three I had called non-blocking. A grep for the word block is the wrong instrument for a census of blocking.

The 17th hook was the audit's own thesis, running until this afternoon

That prompt-judge is worth reading, because it is the recursive stop the audit describes, built on purpose. It instructs: if Claude "says it is stopping, pausing, queuing work, leaving an item for later, asking whether it should continue" then return INCOMPLETE and direct Claude to perform the remaining work. Its reason text is fed back as Claude's next instruction.

Two costs follow. A type: "prompt" hook spends a full model call on every stop, which no script in the chain does. And a judge told to refuse any stop that mentions queuing will refuse stops it cannot clear. The session log for this machine records it looping about 12 turns on a blocked external dependency before it was switched off, which is that session's report rather than a measurement of mine.

The audit asked for recursive stopping to be removed from two scripts that do not exist. The real instance was a prompt-judge, and Brandon had already removed it hours before this page was first written.

The chain does not short-circuit, so the cost compounds

Whether a block at one gate stops the rest from running decides how expensive an unmet stop is. This session answered it by accident. On one Stop event, gates at positions 5, 6, 8 and 13 all blocked and all reported, so the chain ran past the block at position 5 and kept going to position 13.

That means a single unmet stop can collect refusals from up to fourteen gates at once, and each refusal costs a fresh turn carrying the whole conversation. Bounding only the first blocker would do nothing. Every gate needs its own counter, which is what makes the block-block-pass test the first item of Phase 1 rather than a detail inside it.

These counts are a snapshot, and one moved during the review

Hook counts change while sessions run. The first read at 13:0x returned 17 Stop entries. A re-read at 13:41 returned 16, because settings.json was written at 13:28:06 by one of the two other sessions live on this machine, which left a backup named settings.json.bak-before-guitar-evaluator-off-2026-10-17.

Diffing that backup shows what was removed: the 17th Stop hook was an LLM prompt-judge, a {"type": "prompt"} entry rather than a script, and it was turned off at Brandon's request. Its verbatim copy sits at hooks/DISABLED-guitar-completion-evaluator-2026-10-17.json. So Stop held 17 executable hooks before the edit and 16 after. The 14-gate blocking census below is unaffected, and all 14 Python gates are genuinely wired to Stop.

An earlier pass of this page called that entry a blank placeholder, which was wrong. The extraction read each hook's command key, and a prompt-judge hook has none, so a live model-call hook was recorded as an empty string.

One gate is already solved, and it is the pattern to copy

An earlier pass of this page called completion_stop_gate.py a silent blocker and read the comment at its line 301 as a live defect. Both readings were wrong. It blocks with return 2 through sys.exit(main()), and line 302 writes the refusal reason to stderr before it does.

That line-301 comment documents a bug that was already fixed on 2026-09-14, when 45 refusals reached Claude with no reason because the text went to stdout. A regression test ships with the fix and passes today, measuring exit 2 with 596 characters of stderr.

It is also already bounded, at HARD_CAP = 25 with a COMPLETION_GATE_CAP override, and its counter is keyed per session. It even carries the fix for the trap the other thirteen will hit: on 2026-09-14 every counter read 1, because the refusal feedback was itself being counted as a fresh user record and resetting the count. So the real target is the 13 gates with no bound, and the work is to copy this one rather than invent a mechanism.

The baseline grew, and its biggest item was uncounted

The audit put the per-turn baseline at 40,401 tokens. Measured today it is 49,989, and the growth is almost entirely skill descriptions.

ItemAuditMeasuredChange
Skill descriptions23,13441,486+79%
Project CLAUDE.md4,1253,535−14%
MEMORY.md10,4384,968−52%
Global ~/.claude/CLAUDE.md2,704absentstale line
Auto-loaded total40,40149,989+24%
POINTERS.mdnot counted53,281retrieval only

That table holds two findings. The first is that 169 skills stay enabled against 27 disabled, so the roster grows faster than it is pruned, and 150 of those skills carry a front-matter description averaging 276 tokens each.

The second is POINTERS.md at 53,281 tokens, larger than the entire audited baseline. It is absent from the audit, and it is also absent from this session's own context, because it is reached by grep rather than loaded. That makes it the one piece of the system already built the right way, and the pattern the skill roster should copy.

The engine is empty rather than half-built

The audit reports a CLI that could inspect and import files with map, generate and write-midi missing, and major components left as stubs. Every file in the skill, with byte sizes:

  7035  SKILL.md
  5771  reference/berklee-reharmonization.md
  4917  reference/corpus-map.md
  7330  reference/ted-greene-modern-chord-progressions.md
 16916  schemas/reharm-ir.schema.json
     0  src/reharm_craft/__init__.py
     0  src/reharm_craft/analysis/__init__.py
     0  src/reharm_craft/exporters/__init__.py
     0  src/reharm_craft/importers/__init__.py
     0  src/reharm_craft/instruments/__init__.py
     0  src/reharm_craft/search/__init__.py
     0  src/reharm_craft/transforms/__init__.py
     0  src/reharm_craft/verify/__init__.py

empty dirs: bin/ scripts/ tests/{unit,property,integration,golden}
            examples/reports fixtures/{incomplete_evidence,midi_only,gp_full}

Zero bytes of executable Python, and no reharm*.py anywhere under ~/Projects, ~/.claude or ~/New Desktop. The schema and the three reference documents are real and usable.

The skill already says so

SKILL.md line 41 marks bin/reharm and the Python package not implemented, and line 44 states the skill answers analysis only "until bin/reharm doctor exists and exits 0." The documentation is accurate. The audit's description of a working import path is what does not match.

This makes the work cheaper than the audit assumed. There is no half-built code to repair, and there is a settled 16,916-byte IR schema to build against.

The session totals are not reproducible here

Scanned all 621 transcripts across all 24 project directories under ~/.claude/projects, filtering on in-record timestamps rather than file modification time.

sessions with 2026-09-10 activity : 1
assistant turns                   : 70
real user messages                : 11
cache_read tokens                 : 43,786,490
output tokens                     : 52,533
turns per user message            : 6.4
FigureAuditMeasured hereRatio
Concurrent sessions12112×
Assistant turns1,6107023×
Cache-read tokens496,941,52043,786,49011×
Turns per user request776.412×
Cache-read per turn~556,000625,521agrees

Retention does not explain the gap. cleanupPeriodDays is 36500, transcripts run from 2026-05-05 to 2026-10-17 across 98 distinct dates, and all thirty days of September are present.

Three open readings, and this machine cannot separate them
  1. The twelve sessions ran on the MacBookPro. Brandon runs two Macs and this check covers one, so a second-Mac scan settles it.
  2. The counts are inflated. 1,610 turns against a measured 70 is a factor of 23.
  3. A different counting rule. Counting every user-role record including tool results moves the denominator the opposite way and still does not reach 1,610.

The bottom row of the table is what matters for the remediation, and it holds: the per-turn cost is confirmed by two independent measurements. The aggregate totals are labelled unverified and are not quoted as fact until the MacBookPro is scanned.

The three-phase plan

Ordered so that the cheapest fix with the widest effect comes first, and so that no phase's evidence depends on a phase that has not closed.

Phase 1

Bound the supervisor and shrink the baseline

Built and wired on 2026-10-17, with the code and the runtime test named below. hooks/_stop_budget.py holds the shared budget, hooks/stop_dispatch.py replaces the 14 separate Stop entries in settings.json with one that runs every gate and arbitrates, and hooks/test_stop_budget.py drives the real gates to prove it. Measured: three gates refusing one stop spend one unit, exit codes run [2,2,2,0,0] against a budget of 3, the exhausting turn lists all three unresolved conditions and marks them STAYS FAILED, alternating gates across turns still share one budget, and a new human message restores it. Stop entries went from 16 to 3. Before-baseline for the comparison still owed: 508 assistant turns, 9 refusal events across 7 distinct gates, 271,161 cache-read per turn.

Baseline cut DONE 2026-10-17, measured. Front-matter descriptions went 41,486 to 6,459 tokens across 143 trimmed SKILL.md files, taking the auto-loaded baseline from 49,989 to 14,962. The trigger phrasings moved into each skill body under a "When to use this skill" heading, so grep still reaches them while the per-turn load does not: eight sampled trigger phrases each still resolve to exactly one file. Tool kept at skills/_shared/trim_skill_descriptions.py and idempotent on a second run, 147 backups written, nothing removed. That figure is estimated from file bytes at chars/4, so no cache-read saving is proven; the usage re-measure is queued as q-2026-10-17-d74d66.

Give the Stop chain one session-wide retry budget shared by every gate. Each gate records its precise failure reason against that shared budget; when the budget is spent, the chain reports every unresolved condition at once and lets the session end, with any failed musical verification staying visibly marked as failed. Copy the HARD_CAP mechanics completion_stop_gate.py already uses, and avoid the trap it documents, where every counter read 1 because the refusal feedback was itself counted as a fresh user record. Then move the long trigger lists out of each front-matter description into the skill body, following the POINTERS.md pattern.

Why shared rather than per-gate. Thirteen independent counters at two retries each admit up to twenty-six extra turns, and more when gates alternate which reason they block on. Because the chain does not short-circuit, all thirteen fire on the same Stop event, so per-gate ceilings multiply exactly where they should cap.

The Stop chain goes first because every later phase ends in a stop, so an unbounded block loop on a phase-2 stop costs a multiple of what it costs today. Edit the hook scripts rather than settings.json, which is a contested file and where the audit records a permissions deadlock.

Exit: a test that blocks on alternating reasons across several gates still ends the session within the shared budget; measured auto-loaded baseline under 25,000 tokens, down from 49,989; cache-read per turn re-measured against today's 625,521.
Phase 2

Build the engine against the schema, tests first

A greenfield build with the data contract already settled by the 16,916-byte IR schema. Minimum surface: doctor, import, map, generate, write-midi, verify. doctor exits 0 only when the other five run on the bundled fixture.

The note-level tests are written before the generator that must pass them, because the audit records two gates that passed on the wrong question: a scorer that rewarded unchanged harmony with 102, and a melody collision check that compared against chord roots and so let compound minor ninths through. Each test is run against its recorded bad input first and must fail there before it is trusted on good input.

Exit: bin/reharm doctor exits 0, and every note-level test fails on its recorded bad case then passes on the fix. Watch: a fixed-shape exporter writes a full-size empty MIDI and exits 0, so verify reopens the file and counts notes against the input.
Phase 3

One song, then the outstanding scan

One Shiner song end to end: mapped, three harmonically distinct candidates, melody and bass preserved, MIDI exported, reopened, and passing the phase-2 tests with named bars and pitches. One song per pass. Then the MacBookPro transcript scan, which closes the only diagnostic this machine cannot run.

The showcase is rebuilt from the phase-1 and phase-3 measurements side by side: baseline before and after, cache-read per turn before and after, block counts before and after, and the per-candidate test results.

Exit: three candidates on disk, tests passing, page live at http 200, fraud check green. Goal metric: model turns per verified playable arrangement, undefined today because the count of verified playable arrangements is zero.

Provenance

Every measured figure on this page was produced by a command on this machine on 2026-10-17. Figures in the audit column are quoted from the source document and were not re-derived. The sibling document holds the full command list.

ClaimHow it was measured
46 hook entries, 16 on Stoppython3 over ~/.claude/settings.json
17th Stop hook was an LLM prompt-judge, now disabledsettings.json.bak-before-guitar-evaluator-off-2026-10-17 diffed against current, plus hooks/DISABLED-guitar-completion-evaluator-2026-10-17.json
14 of 14 Python Stop gates blockper-file census of return 2, exit(2) and "decision", plus 5 gates observed blocking this session
Stop chain does not short-circuitpositions 5, 6, 8 and 13 all blocked on one Stop event in this session
completion_stop_gate blocks, reports and is boundedreturn 2 at lines 294 and 303, stderr at 302, HARD_CAP at 38; gate_repair_regression_2026_09_14.py passes
Three audit-named scripts absentfind ~/.claude -name <each>
169 enabled, 27 disabled skillsls over skills/ and skills_disabled/
41,486 description tokensregex over front-matter in 150 SKILL.md files, chars/4
Context file sizeswc -c, chars/4
POINTERS.md not auto-loadedabsent from this session's own context
Engine byte sizesfind . -type f -exec ls -la
Sept 10 transcript figurestimestamp-filtered scan of 621 transcripts, exact usage fields
Retention not the causecleanupPeriodDays = 36500, 98 dates present
Accuracy boundaries

Token counts from files are estimates at chars/4, correct to within roughly 10% for English prose. Transcript token counts are exact, read from each record's usage field.

Labelled unverified: the twelve-session count, the 1,610-turn count, the 496,941,520-token total, the 251 blocked stops, and the attribution of 204 of them to three scripts that do not exist here.