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.

tablerows everproduction writerproduction readerverdict
settings_overrides0none (setOverride called only from tests)pretool/settings-override-resolver, on every PreToolUsereader and schema removed together
locks0none (the acquire path was never built)one GC line in session-registrar sweep()GC line and schema removed
worktree_links0none (#3315 deleted the last one)noneschema removed
sessions3,855registrar, finalizer, heartbeatregistrar (crash sweep, peer warning), skill analytics FKstays, 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 blockbefore (ms/call, mean)after (ms/call, mean)
pretool/settings-override-resolver46not registered
lifecycle/webhook-forwarder (untouched, the #3315 trap)4857
snapshot files written1no 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.