Read hook payload from fd 0, not /dev/stdin
Claude Code delivers the hook payload on a socket. Opening it by path
('/dev/stdin' -> /proc/self/fd/0) fails with ENXIO, so bash-guard.mjs threw on
every invocation and its bare catch exited 0 silently. The guard looked like it
was never dispatched; it was dying on line 29 each time.
readFileSync(0) is read() on the descriptor with no open(), which works on a
socket. The catch now logs instead of swallowing, so this failure mode can never
again masquerade as non-dispatch.
Adds test-bash-guard.py, which drives the guard over a socketpair. A pipe would
not reproduce the bug, so the socket is load-bearing. Verified failing against
the pre-fix guard (silent, no output) and passing after.
Removes the five diagnostic probes and probe.mjs; they served their purpose.
Bumps to 1.0.2 because the plugin cache is keyed by version and an unchanged
version silently skips reinstall.
This commit is contained in:
@@ -24,10 +24,17 @@ const deny = (reason) => {
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
// Read fd 0 directly. Claude Code delivers the payload on a socket, and opening it by path
|
||||
// ('/dev/stdin' -> /proc/self/fd/0) fails ENXIO. readFileSync(0) is read() with no open().
|
||||
let input;
|
||||
try {
|
||||
input = JSON.parse(readFileSync('/dev/stdin', 'utf8'));
|
||||
} catch {
|
||||
input = JSON.parse(readFileSync(0, 'utf8'));
|
||||
} catch (e) {
|
||||
// Never swallow this silently: a guard that dies here is indistinguishable from one that
|
||||
// never ran, which is exactly what hid the ENXIO bug for three sessions.
|
||||
try {
|
||||
appendFileSync('/tmp/bash-guard-mode.log', `stdin-FAILED err=${e?.message ?? e}\n`);
|
||||
} catch {}
|
||||
process.exit(0);
|
||||
}
|
||||
const command = input?.tool_input?.command ?? '';
|
||||
|
||||
Reference in New Issue
Block a user