The guard that guarded the guard

posttool/write/stale-import-detector runs after every Write and Edit and is named in this repo's own rules as the thing that catches a rename's stale importers. Measured on 2026-08-29 while wiring asyncRewake: it had never once found one. Type a pattern and watch the shell guard decide.

1. Why every call returned nothing

SAFE_ARG_RE = /^[a-zA-Z0-9._/~=:@-]+$/ is the right guard for a path handed to a shell string. A regex is not a path: it needs (, |, \, *. The assertion threw on every call, the catch returned [], and the hook answered {"continue":true,"suppressOutput":true} for every Write since the guard was added.

2. Why the tests were green

The unit test mocks child_process, so grep never runs and the assertion in front of it is never exercised with the real pattern. The mock returns the references the test wants; the block path is proven against a shell that does not exist. A second, quieter defect hid behind the first: CC hands hooks CLAUDE_PROJECT_DIR as a realpath (/private/var/... on macOS) while tool_input.file_path keeps the caller's spelling (/var/...), so the same-subtree check compared two spellings of one directory.

3. The fix, and the test that would have failed on day one

piecebeforeafter
grep callone shell string, regex through assertSafeShellArgexecFileSync('grep', [args]): an argument vector, no shell, the pattern is data
pathsproject dir from env, file from the tool input, as givenboth through realpathSync when they exist
testsone file, shell mockedplus stale-import-detector.real-grep.test.ts: real grep on a temp fixture with three same-subtree importers must block; one importer must stay silent
through CChook fires, 2 ms, silentwith #3800's asyncRewake wiring: the block reaches the next request as a system reminder

Class: a fix present and never reached, and a test that measures a mock. The verdict a rewake can carry is only as good as the verifier that establishes it; this one established none.