How Event Hooks Work
Hook event lifecycle
Section titled “Hook event lifecycle”Normal flow (no block):
- Claude requests a tool call (e.g. Bash)
- Claude Code finds matching
PreToolUsehooks in settings - Claude Code runs the handler, piping JSON to stdin
- Handler exits
0→ Claude Code executes the tool - Tool returns a result to Claude Code
- Claude Code runs any matching
PostToolUsehandlers - Handler exits
0→ Claude receives the tool result
When a handler blocks:
- Claude requests a tool call
- Claude Code runs the
PreToolUsehandler - Handler exits
2→ Claude Code does not execute the tool - Claude receives a “tool call blocked” message instead
Exit code reference:
| Exit code | Meaning |
|---|---|
0 | Proceed — tool executes (PreToolUse) or result is returned (PostToolUse) |
2 | Block — tool is not executed; Claude receives blocked message |
| Any other | Treated as 0 — proceed |
-
Lifecycle events: Hook events fire at specific points in the session—before/after tool use, permission checks, session start/end, file changes, subagent dispatch, compaction, and MCP elicitation. Each event is named after what triggered it (e.g.,
PreToolUsefires before any tool runs). -
Matcher groups: For each event, the harness iterates over configured matcher groups and dispatches matching handlers. A matcher is an optional pattern (regex or literal) that filters on tool name, agent type, file path, or rule string—not all events support matchers. Matchers are per-event;
PreToolUsematchers match tool names,FileChangedmatchers match file globs, etc. -
Four handler types:
command(shell),http(webhook),prompt(single-turn LLM),agent(sub-agent with tools). Each receives the hook input as JSON on stdin (command, prompt, agent) or POST body (http). Handlers return decisions:"approve","block","emit"context, or no decision (continue normally). -
Scope precedence: Hooks resolve in order: session frontmatter (highest priority, temporary) → skill/agent frontmatter → settings.json (lowest). A hook matching the same event/matcher across scopes is deduplicated by content; duplicates run once per handler type per source.
-
Decision flow:
PreToolUseandPermissionRequestaccept decisions. A handler returning exit code 2 (or JSONdecision: "block") blocks the action.prompthooks returning{"ok": false}block;httphooks using non-2xx response block. Once a handler blocks, downstream handlers for that event are skipped. -
Async execution: Handlers can run asynchronously (
async: true) without blocking the turn, or synchronously block the session.asyncRewakehandlers resume the model if they exit with code 2. -
Environment & security: Session env file (
$CLAUDE_ENV_FILE) is writable bySessionStart,CwdChanged,FileChangedhooks to inject variables. HTTP hooks enforce SSRF protection: URLs must matchallowedHttpHookUrlsand env vars used in headers must be listed in both the hook’sallowedEnvVarsAND settings.jsonhttpHookAllowedEnvVars. -
Skill-only features:
once: true(fire once, then remove),if: "Bash(git *)"(permission-rule filter) are skill/frontmatter only. Built-in hooks (internal) and function hooks (programmatic) are additional types not persisted to settings.