run-hook.mjs is the single entry point every one of the 219 hooks passes
through. It has three ways to degrade. Two of them warn. The third was silent, and while fixing that
one a fourth problem turned up that is worse: the warning itself crashed.
| path | before | after |
|---|---|---|
| stdin truncated at N KB | threw ReferenceError | warns, exit 0 |
| truncated JSON unparsable | threw ReferenceError | warns, exit 0 |
100ms elapsed, empty {} | SILENT, exit 0 | warns when a pipe sent 0 bytes |
process.stderr.write(`... for hook "${name}" ...`) // no local `name` exists
node --input-type=module -e 'typeof name' -> "undefined" (typeof is safe)
node --input-type=module -e '`${name}`' -> ReferenceError
The hook name lives in
hookName (:227). TypeScript resolved the bare name to the
deprecated DOM global, so it reported only a soft "'name' is deprecated" hint, which reads as
cosmetic. In Node ESM there is no such global, so the template literal throws. That write sits
outside the try/catch beneath it, so the >512KB guard added for image pastes (#620) killed the
hook with an uncaught exception rather than degrading it.
Four sites interpolate a bare name. Only three are bugs. At :221,
name is the parameter of isHookDisabled(name, overrides) and is correct,
which is why the diagnostics flagged only :376, :400 and :407.
A blanket replace would also have hit a site where hookName is not yet in scope
(declared at :227, after it), converting a working line into a temporal-dead-zone error.
100ms is a HANG GUARD, and PreToolUse has a <50ms budget.
A longer wait trades a silent failure for a slow one.
Discriminator instead: process.stdin.isTTY
TTY no payload was ever piped -> legitimate -> stay quiet
pipe + 0B a payload was expected and lost the race -> WARN
revert ONLY run-hook.mjs exit=1 AssertionError: expected not to match /ReferenceError/ ReferenceError: name is not defined with the fix exit=0 4 passed full hook suite exit=0 311 files, 7,523 passed typecheck / builds / counts / manifests / security all 0
The pre-fix run does not merely fail, it
prints the exact ReferenceError, which is the crash proven empirically rather than argued.
A note against my own process. The verification script failed on its first run
because set -e aborted at the step that was SUPPOSED to fail, before rc=$?
could read it, and the abort left the fix sitting in a git stash. That is the same defect class this PR
fixes, made three separate times in one session: grep -c || echo 0, then
vitest | tail returning tail's status, now set -e eating an expected failure.
Every intentional-failure step in the final script captures with || rc=$?.