The Stop hook that read a payload it never got

skill/cross-instance-test-validator, #3804 · every unit test green, zero production verdicts · 2026-08-30

What was wired vs what arrived

hooks.json registered it here:

"Stop": [ ...
  { "command": "node",
    "async": true, "asyncRewake": true,
    "args": [..., "skill/cross-instance-test-validator", "--rewake"] }
]

the handler started with:

const filePath = input.tool_input?.file_path || '';
const content  = input.tool_input?.content  || '';
if (!filePath || !content) return outputSilentSuccess();

A Stop payload is {session_id, cwd, stop_hook_active, ...}. It carries no tool_input, so the first guard returned silent success on every invocation, hook-timing recorded ok:true, and the block path ("Missing test coverage for new code") had never executed in production. The verdict probe measured it: trip=silent, carried as the suite's one xfail.

Why every test was green

// the old unit test built its own input
function createFileInput(filePath, content) {
  return { tool_name: 'Write', session_id: 'test-session-123',
           tool_input: { file_path: filePath, content } };   // a PostToolUse shape
}

What it reads now: the session's own edit history

.claude/state/edit-history.jsonl        (written by posttool/write/edit-history-tracker)
  {"t":1756540000000,"f":"/repo/src/svc.ts","tool":"Write","sid":"<this session>"}
  {"t":1756540001200,"f":"/repo/src/svc.ts","tool":"Edit", "sid":"<this session>"}
  {"t":1756540002000,"f":"/repo/docs/x.md",  "tool":"Write","sid":"<other session>"}  <- ignored (#2919)

Stop  ->  entries with sid == session_id
      ->  dedupe, drop test files / non-code / deleted files
      ->  read each file from DISK, extract exports
      ->  find its test file (sibling, sibling __tests__/, or MIRRORED tree)
      ->  no test file      : BLOCK   (exit 2 under --rewake, Claude is woken with the list)
          test file, gaps   : WARN    (systemMessage; Stop-legal, unlike a PostToolUse-stamped context)
          covered / nothing : SILENT
      ->  remember reported files in .claude/state/test-coverage-reported-<sid>.json

Try the verdict

Same rules as the handler, in the same order. Toggle the state of one file this session wrote and read the verdict.

payload
the file on disk
SILENT

Verdict probes, before and after this change

before (main)15 pass · 1 fail · 1 known-dead
after17 pass · 0 fail · 0 known-dead
probebeforeafterwhat changed
stop-cross-instance-missing-tests-blockxfail (silent)trip=block
control=silent
trip seeds edit-history with the runner's session id; control adds src/svc.test.ts
egress-staged-download-run-askfail on this hosttrip=ask
control=silent
pinned ORK_SANDBOX_ENABLED=unknown: since #3808 the guard reads the machine's sandbox posture and stands its ASK tier down when the sandbox is on. Same pin added to bypass-mode-ask-gate.test.ts, which failed on main for the same reason.

The second row is the same lesson from the other side. A test that reads the machine it runs on passes in CI and fails on a developer's laptop, or the reverse. Pin the input the test is not about.

Numbers