If your Claude Code hook exits with code 1 and the tool call still runs, that is usually expected: for most hook events, exit code 1 is a non-blocking error, not a denial. To stop a tool call before it runs, use a PreToolUse hook and either exit with code 2 or return a valid, event-specific JSON denial on stdout. The right choice depends on the event.
Why exit code 1 does not stop the tool call
Claude Code distinguishes an error from a blocking decision. For most hook events, when there is no valid decision JSON, exit code 1 is reported as a non-blocking error and the action proceeds. Anthropic’s Hooks reference states: “For most hook events, exit code 2 is the only exit code that blocks through the code alone.”
As an Amazon Associate I earn from qualifying purchases.
That does not mean every event follows the same rule. Some events use structured decisions or have lifecycle-specific behavior, so check the event’s contract rather than treating any nonzero exit as a veto.
Choose the blocking method for your event
For a command PreToolUse hook: exit 2
PreToolUse runs before a tool call. For a simple command hook on this event, exit with code 2 to block the call. Write the explanation you want Claude Code to show to stderr; when there is no structured blocking reason, stderr supplies that explanation.
#1 Best Overall
For event-specific control: return a denial in JSON
Some hook events support structured decisions. For PreToolUse, the hook can return valid JSON on stdout with the supported deny decision and a reason. Use the exact fields allowed by the event’s output schema. Keep stdout limited to the JSON object—send debug messages, banners, and other diagnostics to stderr or a log file so they do not interfere with parsing.
Do not assume code 2 works for every event
PermissionRequest does not use exit code 2 as a denial; it needs its supported decision object. WorktreeCreate treats any nonzero command exit as failure, which is a different outcome from an ordinary hook denial. Other events may not be able to block because the action has already happened or because the event does not support blocking. Consult the event row in the official reference before changing exit behavior.
Exit-code guide
| Hook response | Typical effect | What it means |
|---|---|---|
0, no decision JSON |
Normal flow continues | Success or no decision; not a denial. |
1, no valid decision JSON |
Non-blocking error for most events | The action generally proceeds. |
2 on a blockable event |
Blocking error | For PreToolUse, the tool call is blocked. |
| Valid event-specific JSON | The supported decision is applied | Use the event’s schema and keep stdout clean. |
This is a general guide, not a replacement for the event-specific behavior documented in Anthropic’s reference.
Free tools Windows power users keep installed
One-click scans. No signup required.
Diagnose why the hook is not blocking
- Confirm the event runs before the action. If you need to prevent a tool call, check that the hook is configured for
PreToolUse. APostToolUsehook runs after the tool succeeds, so it cannot prevent or undo that call. The hooks guide explains event configuration and timing. - Check the settings scope and matcher. Verify the hook is in the settings scope you intend, the configured matcher targets the actual tool name, and capitalization is correct. Hook settings define event names, matchers, and commands; the reference describes matcher behavior and the JSON input supplied to hooks.
- Use one supported denial method. For a straightforward command-based
PreToolUsehook, write a clear message to stderr and exit2. For event-specific control, emit the valid denial JSON instead. Exit1by itself does not replace either mechanism. - Keep machine-readable output clean. If using JSON, remove startup banners and debug
echostatements from stdout. Direct diagnostics to stderr or a log file. - Verify the command actually ran. Check Claude Code’s hook/debug output and temporarily log the event, matched tool, exit status, stdout, and stderr. Confirm the script path exists and is executable. Anthropic’s guide describes where hook failures appear and recommends logging for additional detail.
- Check for timeouts and version-specific behavior. A timed-out command hook generally does not block a
PreToolUsecall; the call proceeds through the normal permission flow. If you rely on recently introduced fields or behavior, confirm that your installed Claude Code version supports them.
What the exit code alone cannot tell you
A non-blocking error does not prove that the hook failed to run. It may have run and returned code 1 without a blocking decision. The same visible result can also come from a matcher that did not match, a command that could not start, a timeout, or malformed structured output.
To pinpoint an individual configuration, you need the hook event name, relevant settings entry and matcher, script, Claude Code version, and captured hook/debug output. Without those details, changing only the exit code may fix the signal but leave a timing or configuration problem untouched.
Quick Recap
Best Value
Rank #3
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




