Hooks & Automation
Guarantees, not suggestions — a hook runs every time, whether or not Claude remembers to.
Advanced
| Event | Fires | Typical use |
|---|---|---|
| PreToolUse | before a tool call runs | block a dangerous command outright, or require confirmation for a pattern |
| PostToolUse | after a tool call succeeds | auto-format a file the moment it's edited, log every command run |
| UserPromptSubmit | before Claude processes what you typed | inject extra context, or reject a prompt outright |
| SessionStart | when a session begins or resumes | print a status banner, load environment-specific context |
| Stop | when Claude finishes responding | run the full test suite before letting the turn actually end |
| SubagentStop | when a subagent finishes | validate a subagent's output before its summary reaches the main session |
settings.json/settings.local.json
files covered in Claude Code CLI Basics — project-level
hooks are meant to be committed, exactly like a repo's git hooks. A matcher
scopes a hook to specific tools (e.g. only Bash) using a tool name, several
names separated by |, or a regex.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh" }
]
}
]
}
}
0 means allow,
2 means block and treat stderr as the rejection reason shown to Claude, any
other code is a non-blocking error. A PreToolUse hook can also return
structured JSON with an explicit permissionDecision of deny,
allow, or ask instead of relying on the exit code alone.
#!/bin/bash
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
echo "Blocked: rm -rf is not allowed by this project's hooks." >&2
exit 2
fi
PostToolUse fires after the tool already ran,
so it can flag or fix, but not prevent. PreToolUse, UserPromptSubmit,
and Stop are the ones where blocking actually makes sense.
--no-verify past. Start narrow:
enforce the one or two things that have actually caused a real problem before, not every
theoretical one.