Hooks
Your own code, run automatically at lifecycle events. The hard-enforcement escape hatch: an instruction in CLAUDE.md is a request; a hook is a guarantee.
Analogy โ CI hooks / git pre-commit hooks for the agent. They fire on an event whether or not anyone remembers to ask. A linter that runs after every write doesn’t depend on the model choosing to lint.
Mechanism
Configured under the "hooks" key in settings.json, three levels deep:
event โ matcher group โ handler. Handler type is command (deterministic
shell), http, mcp_tool, prompt, or agent (experimental).
Command hooks receive JSON on stdin and signal via exit codes: 0 =
success (stdout JSON processed as a decision), 2 = blocking error (stderr fed back to
Claude, action blocked), any other = non-blocking.
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "./guard.sh" }]
}],
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "prettier --write \"$CLAUDE_FILE\"" }]
}]
}
Event names & the deny decision
SessionStart, UserPromptSubmit, PreToolUse, PostToolUse,
PostToolUseFailure, Stop, SubagentStart/Stop,
InstructionsLoaded, PreCompact/PostCompact, and the team events
TeammateIdle/TaskCreated/TaskCompleted.
PreToolUse can return hookSpecificOutput.permissionDecision: "deny" | "allow" | "ask" | "defer".
The if field accepts permission-rule syntax (e.g. "Bash(git push *)") so you don’t spawn
a process you don’t need. Output capped at 10,000 chars; default timeout 600s (UserPromptSubmit 30s).
When to reach for it
PreToolUse with a deny decision.TaskCompleted).When not to
Best practices
if field to avoid firing on irrelevant tool calls./hooks; remember managed settings can force or restrict them..claude/rules/ file is just memory.
Three different things wear the word “rule” — see the corrections.