What Is This Note?
This is a dated note on our February 2026 guide, "Claude Code Hooks: 6 Production Patterns (2026)". The idea of the guide was sound: use hooks to run checks automatically while an agent works. Its configuration examples were not. They used a shape the settings file does not accept, a variable that does not exist and an exit code that does not block, so a hook built from them would have failed or done nothing.
What Was Wrong With the Original Examples?
| In the February 2026 guide | What the official reference shows | Status |
|---|---|---|
| Hooks defined with a handler object per event | Each event takes matcher groups; each group has a hooks array of entries with a type and a command | Corrected |
| Commands using a $FILEPATH variable | Input arrives as JSON on stdin; the script reads the fields it needs | Corrected |
| Scripts exiting with code 1 to block an edit | Exit 1 is a non-blocking error and the action proceeds; exit 2 blocks | Corrected |
| Scripts reading a filePath field | Tool details sit under tool_input in the JSON input | Corrected |
| Hooks released in early 2026 | Not supported by a source we could check | Withdrawn |
| 12 hook events, cited to a third-party blog | The event list changes between versions | Withdrawn |
What Does a Correct Minimal Example Look Like?
A correct hook has two parts: an entry in the settings file and a script that reads JSON from stdin. This settings entry runs a script after every edit or write:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "/path/to/lint-check.sh"
}
]
}
]
}
}
And this pair blocks one kind of Bash command before it runs. The settings entry attaches a script to every Bash call:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/path/to/block-drop-table.sh"
}
]
}
]
}
}
And the script blocks the command by exiting with code 2:
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q "drop table"; then
echo "Blocked: dropping tables is not allowed" >&2
exit 2
fi
exit 0
Both follow the examples in the Claude Code hooks reference and hooks guide, checked on 2 October 2026 and again on 18 October 2026. Make the script executable, and test that it really blocks before you rely on it. By default, a hook that fails to run, for example because its script path is mistyped, does not block anything: Claude Code carries out the action, and on events such as PreToolUse it only shows a hook error notice in the transcript. To make a failed policy hook block the action instead, add "onFailure": "block" to its entry, which needs Claude Code v2.1.295 or later.
How a hook decides
From the official reference, October 2026
Event
for example PreToolUse
Matcher
which tool it applies to
Command
reads JSON from stdin
Exit code
2 blocks, 0 continues
What Do We Run Ourselves?
We do not run Claude Code hooks in this repository today. Our gates are git pre-commit hooks, which run 13 automated steps before every commit, and four checks our main branch requires on every pull request. They run no matter which tool wrote the change, which is why we rely on them instead.
Agent hooks versus repository gates
Our view of where each fits
- Run inside an agent session
- Good for fast feedback while the agent works
- Do not run when someone else writes the code
- Run on every commit and pull request
- Apply whoever or whatever wrote the change
- Can be required before merge
This note, in numbers
As of 2 October 2026
claims withdrawn or corrected
correct minimal examples
pre-commit steps we run
required pull request checks
Our Thread-Based Engineering post describes those gates in full. For the programmatic hooks in Anthropic's Agent SDK, see our Agent SDK hooks reference.
Claude Code Hooks: Questions Readers Ask
Common questions about this topic, answered.
What are Claude Code hooks?
Hooks are commands you configure to run automatically at points in a Claude Code session, such as before or after a tool call. They can format files, run checks or block an action. They are configured in a settings file under a hooks key.
Why were the examples in the original guide wrong?
Three reasons. They used a handler shape the settings file does not accept, they read a $FILEPATH variable that Claude Code does not set, and they used exit code 1 to block an action, which does not block it. A hook built from them would have failed or done nothing.
How does a hook receive the file path or command?
As JSON on standard input. A hook script reads stdin and pulls out the fields it needs, for example tool_input.command for a Bash call. There is no environment variable that holds the file path.
Which exit code blocks an action?
Exit code 2. For most hook events, it is the only exit code that blocks by itself, and the text the script writes to stderr becomes the reason. Exit code 1, the usual Unix failure code, is treated as a non-blocking error and the action proceeds. A hook can also exit 0 and print a JSON decision instead.
How many hook events are there?
The original guide said 12, citing a third-party blog. The event list in the official reference has changed between Claude Code versions, so we removed the count. Check the hooks reference for the events your version supports.
When were hooks released?
The original guide said early 2026. We could not support that date, so it is withdrawn. The official documentation and changelog are the place to check the history of the feature.
Does Pixelmojo use Claude Code hooks?
Not in this repository today. Our gates are git pre-commit hooks, which run 13 automated steps before every commit, and four required checks on every pull request. They run no matter which tool wrote the change, which is why we rely on them.
What changed in this post on 2 October 2026?
We cut it down to a dated note. The config examples are withdrawn and replaced with a minimal example in the shape the official reference shows. The release date and the event count are withdrawn, and the post now says plainly which gates we run ourselves.
What changed in this post on 18 October 2026?
One correction. We had said a mistyped script path leaves a hook silently disabled. The reference now says Claude Code shows a hook error notice on events such as PreToolUse, and a new onFailure setting can make a failed hook block the action. By default, a failed hook still lets the action through.
