Claude Code hooks let the call through when they fail: 18 of 989 public guard repos set onFailure
A PreToolUse hook that crashes, times out or exits 1 lets the tool call run. Of 989 public repos with a blocking PreToolUse hook, 18 set onFailure. We tested the three cases and counted what repos do.
Summary
If you use a Claude Code PreToolUse hook as a guard, add "onFailure": "block" to it, and make sure the script blocks with exit 2, not exit 1.
The reason is simple. A guard hook only blocks when it runs and says no. When it fails, the tool call goes ahead. The docs count all of these as failures that let the call through:
- It can’t start. The script path is wrong, or
nodeorpythonis missing. - It exits with a code other than 0 or 2. That includes
exit 1, the usual Unix error code. - It times out. The default is 600 seconds for a command hook.
- It prints invalid JSON.
Since version 2.1.295 (October 8), one field changes that: with onFailure set to "block", any of these failures blocks the call instead.
How many public guards use it? We read the project settings of 5,864 active public repos. 989 have a PreToolUse hook that can block a call. 18 of them set onFailure, about 1 in 55.
How we tested
-
The behaviour. In an empty project, we set up a
PreToolUsehook on Bash and asked Claude Code 2.1.314 (claude -p, Haiku) to runecho ran > ran.txt. Three cases:- A hook that prints
BLOCK: not allowedand exits 1: the command ran. - A hook whose script doesn’t exist, with no
onFailure: the command ran. - The same missing script with
"onFailure": "block": the command was blocked, with the errorfailed; blocking because onFailure is "block".
The first hook also saved its environment variable names and its stdin. The tool input was only on stdin, as JSON. No variable named
CLAUDE_FILE_PATHorCLAUDE_TOOL_INPUTwas set. - A hook that prints
-
The repos. The same sample as our project settings review: 5,864 public repos with at least 5 stars, pushed to since October 7, not forks, as of November 6, 2026. We read every
.claude/settings.jsonin them and the scripts their hooks name. -
Which hooks can block. We read each
PreToolUsecommand hook and the script it calls, without running anything. A hook counts as blocking if it can exit 2 or print adenyorblockdecision. A hook that hands off to code we couldn’t read counts as unclear, and we keep those apart.
Results
How many guards fail closed
| Repos | |
|---|---|
Have a PreToolUse command hook | 1,673 (28.5% of 5,864) |
| Of those, have a hook that can block | 989 (59.1%) |
Of those, set onFailure on a PreToolUse hook | 18 (1.8%) |
Set a timeout on a PreToolUse hook | 675 (40.3% of 1,673) |
Even counting the 376 unclear repos as blocking, the share that sets onFailure stays under 2%. Across all hooks in all repos, onFailure appears 87 times: 85 set to "block" and 2 set to "continue".
Guards that never block
Quick guess: what does a guard do when it prints BLOCK and exits 1? It lets the call through. We found two common ways a guard ends up blocking nothing:
| Hooks | Repos | |
|---|---|---|
Print a block message such as Blocked: or not allowed, then exit 1, with no exit 2 or deny anywhere | 53 | 42 |
Read $CLAUDE_FILE_PATH, $CLAUDE_TOOL_INPUT or a similar variable that Claude Code doesn’t set | 61 | 41 |
A typical one-liner from the first group checks whether the file path contains .env, prints that the edit is blocked and exits 1. Claude Code shows a hook error notice and makes the edit.
The second group is quieter. The variable is empty, so a pattern such as grep -q 'rm -rf' never matches, and the hook exits 0 every time. 25 of those 61 hooks would block with exit 2 if they ever matched.
Guards that would change if you only added the field
110 blocking hooks in 59 repos also exit 1 somewhere, often for a warning or a missing tool. With onFailure: "block", every one of those exits blocks the call too. The docs give the fix: exit 0 where you warn, and print the warning as a systemMessage.
Our picks
What we now do with every guard hook, in order:
- Block with exit 2, or with a JSON deny on exit 0. Exit 1 is a warning that the call ignores.
- Read the tool input from stdin. For example,
jq -r '.tool_input.command'for Bash or.tool_input.file_pathfor Edit and Write. - Add
"onFailure": "block". Then a missing script, a crash or a timeout blocks the call instead of passing it. It works oncommandandhttphooks. - Set a short
timeout. WithonFailureset, a timeout blocks, so a few seconds is safer than ten minutes of waiting. - Test the failure. Rename the script and ask Claude to run
ls. You should seeblocking because onFailure is "block".
Which of your guards would let rm -rf through if its script went missing today?
What to ignore
Adding onFailure everywhere. It has no effect on Stop, SubagentStop, TaskCompleted and TeammateIdle hooks, or on hooks that run with async. On a formatter or a logger, a failure that stops the edit only gets in your way. Put it on the hooks that guard something.
The 0 we found for blockWritesOutsideWorkingDirectories, the permissions setting added in 2.1.314 on October 30. It is a week old, so the count says nothing yet.
Limitations
- Static reading. We ran three test hooks, not the repos’ hooks. A script that blocks through code we couldn’t read is in the unclear group.
- Keyword matching. Exit 1 next to a block message is a strong sign of a guard, but some of those 53 hooks may mean to warn.
- A sample. Active repos with 5 or more stars, as of November 6, 2026, chosen by code search relevance. It is not a random sample.
- One version. We tested on Claude Code 2.1.314. On Windows without Git Bash, a failing shell-form
PreToolUsehook can block even withonFailureunset, so the numbers here describe macOS and Linux behaviour.
Reproduce it
The counts are in hooks-onfailure-2026-11.json, with repo names and hook contents left out. To test one of your own guards, add "onFailure": "block", rename the script it calls, and ask Claude Code to run the tool the hook matches.
For the rest of a repo’s settings file, see what 5,864 public repos turn on in .claude/settings.json. All of our checks are on one page: the Claude Code safety checklist.