A python3 hook whose script is missing blocks every tool call (exit 2), unlike a missing shell script (127)
Summary. The hooks docs say a missing hook script is a non-blocking error: the shell exits 127 and the action proceeds. That holds for /path/hook.sh, but not for the common python3 /path/hook.py form. CPython exits 2 when it cannot open the script, and exit 2 is the documented blocking code. If the file disappears, every matching tool call is refused.
How it happens in practice. The script is deleted or renamed, or the directory it lives in is a git working tree that changes shape (a git rebase or checkout mid-way). With a PreToolUse hook on * or Bash, the session is locked out of its own tools. Each call fails with can't open file '/path/hook.py': [Errno 2] No such file or directory, reported as a blocking hook error, until the file comes back.
Repro.
- Register a
PreToolUsecommand hook:python3 /tmp/does-not-exist.py - Ask for any Bash command.
- The call is blocked with the interpreter's "can't open file" message on stderr.
For comparison, /tmp/does-not-exist.sh exits 127 and the call proceeds, as the docs describe.
$ python3 /tmp/does-not-exist.py; echo $?
python3: can't open file '/tmp/does-not-exist.py': [Errno 2] No such file or directory
2
$ /bin/sh -c /tmp/does-not-exist.sh; echo $?
127
Suggestion. Either document that interpreter-launched hooks can exit 2 when their script is missing, or treat a missing hook script as non-blocking regardless of which interpreter reports it.
Workaround for anyone hitting this. Guard the file's existence in the command itself, so a missing script allows and a real block still passes through:
f=/path/hook.py; [ -f "$f" ] || exit 0; exec python3 "$f"
Environment: Claude Code 2.1.117, macOS 26.6.2, Python 3.11.2.