Zero rows is not dead code: three tables, one live reader, one wrong register row
Issues #3353 and #3937, branch chore/3353-unused-tables-verdict. The #3328 audit read "0 rows ever" as dead code and nearly deleted a live read path. This page records what was actually there, what was measured, what was removed, and the one instruction that was refused because HEAD disagreed with the register.
The three tables, read from the live registry on 2026-09-06
Copied ~/.local/state/orchestkit/sessions.db to a scratch path and queried it: 3,855 session rows (2,927 completed, 903 crashed, 25 running), and nothing ever deletes one, so the ON DELETE CASCADE that could have hidden past rows has never fired.
| table | rows ever | production writer | production reader | verdict |
|---|---|---|---|---|
settings_overrides | 0 | none (setOverride called only from tests) | pretool/settings-override-resolver, on every PreToolUse | reader and schema removed together |
locks | 0 | none (the acquire path was never built) | one GC line in session-registrar sweep() | GC line and schema removed |
worktree_links | 0 | none (#3315 deleted the last one) | none | schema removed |
sessions | 3,855 | registrar, finalizer, heartbeat | registrar (crash sweep, peer warning), skill analytics FK | stays, enforced-FK parent of skill_invocation |
Forward migration 005-drop-unused-tables.sql drops the three; 001 is shipped and is never edited.
What the resolver cost, measured with the real dispatcher
The resolver rode the only matcher: null block in PreToolUse, so it fired on every tool call: spawn node, SELECT from an empty table, write ~/.claude/state/orchestkit/<sid>/active-overrides.json. Nothing in the repo reads that file. Timed through src/hooks/bin/run-hook.mjs against a scratch HOME and a scratch DB, one warmup then n=25, every run exit 0.
| hook in the catch-all block | before (ms/call, mean) | after (ms/call, mean) |
|---|---|---|
pretool/settings-override-resolver | 46 | not registered |
lifecycle/webhook-forwarder (untouched, the #3315 trap) | 48 | 57 |
| snapshot files written | 1 | no state directory created |
Both hooks are async: true, so the turn never waited on them; the cost was one node process per tool call and a file write, spent for nothing. The forwarder's before/after spread is run-to-run noise on a loaded Mac, not a change to that hook.
#3937: the validator nothing ran
tests/plugins/validate-all.sh had no caller. CI runs scripts/ci/run-tests.sh tests/plugins, whose glob takes test-*.sh and test-*.mjs only; the script's own loops globbed ork-* and never looked at plugins/ork; four validators returned 0 on an absent directory; and it failed on the real tree. Deleted, with its two lines in the jq-injection allowlist. The audit table that filed it (docs/audits/gate-fault-arm-audit-2026-09-06.md) keeps the record.
Refused: deleting posttool/heartbeat and the last_heartbeat column
The register row (shared/rules/cc-native-first.md) called last_heartbeat a dead column and the hook marginal drift. At HEAD that is false:
session-registrar.ts sweep(): UPDATE sessions SET status='crashed'
WHERE last_heartbeat < now - 86400 AND status='running'
session-registrar.ts isPeerLive(): row.messaging_socket ? existsSync(socket)
: row.last_heartbeat > now - 300
posttool/heartbeat is the only writer that moves the column after insert. Without it the crash sweep degrades to "started more than a day ago", which on 2026-09-06 would have reaped 1 of the 25 running sessions and hidden it from the peer warning. All 25 running rows carry a messaging socket, so the 5-minute fallback is unreachable today, but the 24-hour sweep is not. The hook stays and the register row now says why. Making the sweep socket-based instead is a redesign, not a residue removal, and belongs in its own issue if wanted.
Removed files
src/hooks/src/pretool/settings-override-resolver.ts src/hooks/src/lib/settings-override.ts src/hooks/src/__tests__/pretool/settings-override-resolver.test.ts src/hooks/src/__tests__/lib/settings-override.test.ts tests/plugins/validate-all.sh
Counts: hooks.json 172 to 171 (global 153 to 152), entries map 183 to 182, async 110 to 109. validate-registry.mjs: closure PASS, dead 0.