Claude Code Hooks: Automate Your Workflow with Lifecycle Events
Run shell commands, HTTP requests, and MCP tools automatically at key points in Claude Code's lifecycle. Build auto-format, lint gates, and audit trails.
Claude Code Hooks: Automate Your Workflow with Lifecycle Events
Key Takeaways#
- Hooks are shell commands, HTTP requests, or MCP tool invocations that fire automatically at defined points in Claude Code's lifecycle — no manual triggering required.
- They receive structured JSON context about the triggering event and can optionally return decisions (allow, block, or modify behavior) via exit codes or JSON output.
- Configuration lives in three levels: hook event, matcher group, and hook handlers — giving you fine-grained control over which tools, files, or patterns trigger which actions.
- Scope ranges from personal (user-level settings) to project (committable
.claude/settings.json) to org-wide (managed policy), so teams can enforce standards consistently. - Practical uses include auto-formatting on save, blocking dangerous shell commands, audit-logging file changes, and preventing context loss during compaction.
What Are Hooks?#
Hooks are event-driven automations attached to Claude Code's lifecycle. When a specific event fires — say, a tool is about to run or a session starts — Claude Code checks whether any hooks are configured for that event. If so, it executes each hook handler in order, passing a JSON payload describing the event context.
A hook can be:
- A shell command (
type: "command") — runs in a subprocess, receives JSON on stdin, returns decisions via exit codes or stdout JSON. - An HTTP request (
type: "http") — sends a POST (or other method) with the event payload to a URL you specify. - An MCP tool call (
type: "mcp_tool") — invokes an MCP server tool directly with parameters derived from the event context.
Hooks are not callbacks in the traditional sense. They execute in their own subprocess or network context. They cannot mutate Claude Code's internal state directly, but they can influence behavior: a hook that exits with code 2 can block the action that triggered it, and a hook that outputs specific JSON can supply additional decisions.
Lifecycle Events#
Claude Code exposes hooks at many points in its lifecycle. Here are the key events:
| Event | Scope | When It Fires |
|---|---|---|
SessionStart | Once per session | When Claude Code starts a new session |
SessionEnd | Once per session | When the session terminates |
UserPromptSubmit | Once per turn | After the user submits a prompt, before processing |
Stop | Once per turn | When Claude finishes a response normally |
StopFailure | Once per turn | When Claude finishes with an error |
PreToolUse | Every tool call | Before a tool executes — can block or allow |
PostToolUse | Every tool call | After a tool executes successfully |
PostToolUseFailure | Every tool call | After a tool execution fails |
PostToolBatch | Every tool batch | After a batch of tool calls completes |
PreCompact | Compaction cycle | Before context is compacted — can block or prep handoff |
PostCompact | Compaction cycle | After context compaction completes |
SubagentStart | Sub-agent spawn | When a subagent begins execution |
SubagentStop | Sub-agent finish | When a subagent finishes |
Notification | Notification event | When a notification is triggered |
PermissionRequest | Permission prompt | Before a permission is requested from the user |
PermissionDenied | Permission denied | After the user denies a permission |
ConfigChange | Config update | When Claude Code settings change |
CwdChanged | Directory change | When the working directory changes |
FileChanged | File system | When a watched file changes on disk |
WorktreeCreate | Worktree | When a git worktree is created |
WorktreeRemove | Worktree | When a git worktree is removed |
Elicitation | User input | When Claude elicits input from the user |
TaskCreated | Task mgmt | When a task is created |
TaskCompleted | Task mgmt | When a task is marked completed |
TeammateIdle | Multi-agent | When a teammate agent becomes idle |
Configuration Structure#
Hooks are configured in a three-level hierarchy:
- Hook event — the top-level key (e.g.,
PreToolUse) that maps to an array of matcher groups. - Matcher group — an object with an optional
matcherpattern and an array ofhooks(handlers). - Hook handlers — individual hook definitions with
type,command/url/tool, and optional settings.
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "validate-bash-command.sh" } ] } ], "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "npx prettier --write $CLAUDE_FILE_PATH" } ] } ] } }
Multiple matcher groups under the same event are evaluated in order. All matching handlers run sequentially.
Hook Locations and Scope#
Where you place hook configuration determines its scope:
| Location | Scope | Git Status |
|---|---|---|
~/.claude/settings.json | All projects for this user | Local only |
.claude/settings.json | Single project | Committable (shared with team) |
.claude/settings.local.json | Single project | Gitignored (personal overrides) |
| Managed policy (admin) | Organization-wide | Pushed via MDM/policy |
| Plugin hooks | Plugin-level | Bundled with the plugin |
| Skill/agent frontmatter | Skill or agent | Bundled with the skill definition |
Project-level settings (.claude/settings.json) are ideal for team-wide hooks like auto-formatting or lint gates. User-level settings (~/.claude/settings.json) are for personal preferences. Managed policy lets enterprise admins enforce hooks that users cannot override.
Matcher Patterns#
Matchers filter which tool calls (or other targets) trigger a hook group. Three formats are supported:
- Exact string:
"Edit"— matches only the tool namedEdit. - Pipe-separated:
"Edit|Write|MultiEdit"— matches any of the listed tools. - Regex:
"Bash(mkdir|rm).*"— matches tools by regex pattern.
The target of a matcher depends on the event type:
| Event | Matcher Targets |
|---|---|
PreToolUse / PostToolUse / PostToolUseFailure | Tool name (e.g., Bash, Edit, Write) |
UserPromptSubmit | Prompt content (regex match) |
PreCompact | No matcher (always fires if configured) |
Notification | Notification type |
FileChanged | File path (regex) |
If no matcher is specified, the hook group fires for all occurrences of that event.
Practical Examples#
1. Auto-Format After Edits#
Run Prettier on every file that Claude edits or writes:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write|MultiEdit", "hooks": [ { "type": "command", "command": "npx prettier --write \"$CLAUDE_FILE_PATH\" 2>/dev/null" } ] } ] } }
The CLAUDE_FILE_PATH environment variable is set automatically by Claude Code when a file-editing tool fires. The hook runs silently — if it fails, Claude Code logs a warning but does not block the workflow.
2. Block Dangerous Shell Commands#
Prevent rm -rf / and similar destructive patterns before they execute:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "echo \"$CLAUDE_TOOL_INPUT\" | jq -r '.command' | grep -iP 'rm\\s+(-[a-zA-Z]*f[a-zA-Z]*\\s+|--)\\\\?/(\\\\s|$)' && exit 2 || exit 0" } ] } ] } }
Exit code 2 is the signal to block the tool call. Claude Code will not execute the blocked command and will inform the user. Exit code 0 allows the tool call to proceed.
3. Audit Log All File Changes#
Write an audit trail for compliance or debugging:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write|MultiEdit", "hooks": [ { "type": "command", "command": "echo \"{\\\"time\\\":\\\"$(date -Iso8601)\\\",\\\"file\\\":\\\"$CLAUDE_FILE_PATH\\\",\\\"tool\\\":\\\"$CLAUDE_TOOL_NAME\\\"}\" >> .claude/audit-log.jsonl" } ] } ] } }
This appends a JSON Lines entry for every file mutation. The log is committable if placed in .claude/, making it easy to track what Claude changed and when.
4. Block Compaction and Inject a Handoff Doc#
Prevent context loss during compaction by forcing Claude to write a handoff document first:
{ "hooks": { "PreCompact": [ { "hooks": [ { "type": "command", "command": "cat <<'MSG'\nCompaction blocked. Write a handoff document summarizing current state before compacting.\nMSG\nexit 2" } ] } ] } }
When a PreCompact hook exits with code 2, compaction is blocked. The stdout text is shown to Claude as the reason, prompting it to create a handoff summary before retrying.
HTTP Hooks#
Hooks can make HTTP requests instead of running local commands. This is useful for sending events to dashboards, Slack webhooks, or custom APIs.
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "http", "url": "https://api.example.com/claude-events", "method": "POST", "headers": { "Authorization": "Bearer $CLAUDE_HOOK_TOKEN", "Content-Type": "application/json" } } ] } ] } }
The event payload is sent as the request body. Header values can reference environment variables for secrets. HTTP hooks that return a non-2xx status code are treated as failures; for PreToolUse events, a 4xx/5xx response blocks the tool call.
Hooks Invoking MCP Tools#
Hooks can call MCP (Model Context Protocol) tools directly. This lets you trigger actions in external services without writing glue scripts:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "mcp_tool", "server": "my-ci-server", "tool": "trigger_pipeline", "arguments": { "repo": "$CLAUDE_PROJECT_DIR", "changed_file": "$CLAUDE_FILE_PATH" } } ] } ] } }
The MCP tool is invoked on the specified server with the given arguments. Environment variable substitution works in argument values, letting you pass dynamic context from the event into the MCP call.
Security Considerations#
- Subprocess isolation: Command hooks run in a subprocess with a scrubbed environment. Sensitive variables like
API_KEYare stripped unless explicitly allowlisted. Never hardcode secrets in hook commands — use environment variables or secret managers. - Exit code semantics: Exit
0= continue, exit2= block, exit1= error (logged but non-blocking). Any other exit code is treated as an error. - Managed hooks: Enterprise deployments can set
allowManagedHooksOnly: truein managed policy. This disables all user- and project-level hooks, ensuring only admin-approved hooks run. Use this when compliance requires strict control over automated actions. - HTTP hook secrets: For HTTP hooks, store tokens in environment variables, not in the settings JSON. The settings file may be committed to version control.
- Input validation: Hook commands receive tool input via
CLAUDE_TOOL_INPUT. Always validate and sanitize this data before using it in shell commands to prevent injection attacks. Preferjqfor parsing JSON input over raw string interpolation. - Ordering matters: Hooks within a group run sequentially. If one blocks (exit 2), subsequent hooks in the same group are skipped. Plan your hook order accordingly.
Hooks are a powerful mechanism for keeping Claude Code aligned with your team's standards. Start with a simple auto-format or audit hook, then layer in guards and integrations as your workflow matures.