Hooks: a rule that cannot be argued with
CLAUDE.md is a request. A hook is a program that runs at a fixed moment and can say no, and no model gets a vote.
Hooks fire on events. There are many, and four cover most real uses.
| Event | When it fires | Good for |
|---|---|---|
PreToolUse | Before a tool call runs, and it can block | Refusing a command or an edit |
PostToolUse | After a tool call succeeds | Formatting a file that was just written |
UserPromptSubmit | When you send a prompt, before Claude sees it | Adding context every time |
Stop | When Claude finishes a turn | Gating on tests actually passing |
The shape of one
A hook is configured in settings, matched to a tool, and points at a command.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": ".claude/hooks/block_rm.py" }
]
}
]
}
}The matcher filters by tool name, so this one only runs for Bash calls. Leaving it out, or using *, runs it on every occurrence of the event.
What the program gets, and what it says back
Claude Code writes JSON to the hook's standard input, describing the tool call:
{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" } }The hook prints JSON back to say what should happen:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}Exit 0 with no output means the hook has no opinion, and the call carries on through the normal permission flow. That asymmetry is the important part: a hook can deny, but staying quiet is not the same as approving.
Hooks and rules together
The two systems do not replace each other, and the docs are precise about where they meet. A hook that allows something does not get past your rules: a matching deny rule still blocks the call, and a matching ask rule still prompts you.
Blocking goes the other way. A hook that exits 2 stops the call before the permission rules are looked at, so the block holds even where an allow rule would have let it through. Refusing is the direction a hook always wins in.
Next lesson writes one and runs it.
- Ask Claude how to set up a hook for your project and read what it suggests.
- Decide which single command in your project should never run without you. That is your first hook.
Slow is fine. Stopping is the only problem.