# Claude Code Hooks Hooks let you run shell commands at specific points in Claude Code's lifecycle. Configured in `settings.json` under the `hooks` key. ## Available hook events | Hook | Fires | Can block? | |------|-------|------------| | `SessionStart` | Session starts or resumes. Runs in both local and cloud environments | — | | `SessionEnd` | Session closes (timeout configurable via `CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS`) | — | | `UserPromptSubmit` | User submits a prompt — before Claude processes it. Useful for pre-processing input, logging, or injecting context | ✅ | | `PreToolUse` | Before a tool call is executed | ✅ | | `PostToolUse` | After a tool call completes successfully | — | | `PostToolUseFailure` | After a tool call fails | — | | `SubagentStart` | A subagent spawns | — | | `SubagentStop` | A subagent completes | ✅ | | `TaskCreated` | A task is added to the task list | — | | `TaskCompleted` | A task is marked complete | — | | `CwdChanged` | Working directory changes during a session | — | | `FileChanged` | A file is modified by Claude | — | | `PermissionRequest` | A permission dialog is shown | ✅ | | `PermissionDenied` | Auto mode classifier denies an action (auto mode only) | — | | `PreCompact` | Before context compression (manual or auto) | ✅ (v2.1.105+) | | `PostCompact` | After context compression | — | | `InstructionsLoaded` | CLAUDE.md or `.claude/rules/` files load (v2.1.69+) | — | | `ConfigChange` | Config changes during a session | ✅ (v2.1.49+) | | `Stop` | Claude finishes generating a response | ✅ | | `Notification` | Claude Code sends a notification | — | `CLAUDE_ENV_FILE` is also available for `CwdChanged` and `FileChanged` hooks (write env vars to it to persist them across subsequent Bash commands). ## Handler types A hook entry can use one of four handler types: | Type | Behavior | |------|----------| | `command` | Runs a shell command. Fast, deterministic, predictable. Default choice | | `prompt` | Lets a smaller LLM decide via prompt. Flexible and context-aware, but adds latency and cost | | `http` | POSTs JSON payload to an external URL with optional auth headers (v2.1.63+) | | `async: true` | Modifier flag — runs the hook in the background without blocking the agent loop | ### `prompt` handler example ```json { "hooks": { "Stop": [ { "hooks": [ { "type": "prompt", "prompt": "Evaluate if Claude should stop: check if all tasks are complete", "timeout": 30 } ] } ] } } ``` ### `http` handler example ```json { "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "http", "url": "https://api.example.com/notify", "headers": {"Authorization": "Bearer $MY_TOKEN"}, "allowedEnvVars": ["MY_TOKEN"] } ] } ] } } ``` `allowedEnvVars` whitelists which env vars from the host environment are interpolated into headers — keeps secrets out of unrelated hook calls. ### `async: true` example ```json { "type": "command", "command": ".claude/hooks/notify-slack.sh", "async": true } ``` Use for fire-and-forget side effects (notifications, logging) that shouldn't slow Claude down. ## SessionStart hooks `SessionStart` hooks run after Claude Code launches, on every session start including resumed sessions. Useful for dependency installation, environment setup, or injecting context. - Hooks can persist environment variables for subsequent Bash commands by writing to the file at `$CLAUDE_ENV_FILE` - Check `$CLAUDE_CODE_REMOTE` to conditionally skip local execution (e.g., only install deps in cloud sessions) - User-level hooks (`~/.claude/settings.json`) only run locally; in cloud sessions, only repo-level hooks (`.claude/settings.json`) execute For cloud-only setup, prefer setup scripts over hooks. See [[Claude Code Web]] for the comparison table. ### SessionStart matcher values | Matcher | When it fires | |---------|--------------| | `startup` | New session | | `resume` | `--resume`, `--continue`, `/resume` | | `clear` | `/clear` | | `compact` | Auto or manual compaction | ### Persisting environment variables Write to `$CLAUDE_ENV_FILE` from a SessionStart hook to make env vars available to all subsequent Bash commands: ```bash #!/bin/bash if [ -n "$CLAUDE_ENV_FILE" ]; then echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE" fi exit 0 ``` `CLAUDE_ENV_FILE` is also available for `CwdChanged` and `FileChanged` hooks. ## Conditional hooks with `if` The `if` field uses permission rule syntax to filter when a hook handler runs within a matched group. Only evaluated on tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied`). Avoids spawning the handler process when the condition doesn't match. ```json { "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "if": "Bash(rm *)", "command": ".claude/hooks/block-rm.sh" } ] } ] } } ``` Here `matcher: "Bash"` filters to Bash tool calls, then `if: "Bash(rm *)"` further narrows to commands starting with `rm`. A command like `npm test` passes the matcher but fails the `if` check and never spawns the handler. More examples: `"if": "Edit(*.ts)"` to lint only TypeScript edits; `"if": "Bash(git push *)"` to guard pushes. ## Hook return values Hooks signal decisions back to Claude Code via exit code or structured JSON on stdout. ### Exit codes (simple path) | Code | Meaning | |------|---------| | `0` | Success. Stdout shown only in verbose mode | | `1`, `3` | Warning. Stderr shown in verbose mode | | `2` | **Blocking error**. Stderr becomes the error message shown to Claude | Exit `2` from a `PreToolUse` hook cancels the tool call; from `Stop`, blocks Claude from stopping; from `UserPromptSubmit`, drops the prompt before Claude sees it. ### Structured JSON output (advanced) Print a JSON object to stdout for fine-grained control: ```json { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "allow", "permissionDecisionReason": "Command validated against allowlist", "updatedInput": {"command": "npm test -- --ci"}, "additionalContext": "Note: 5s query timeout" } } ``` Key fields: - `permissionDecision` — `"allow"`, `"deny"`, or `"ask"` (overrides the normal permission flow) - `permissionDecisionReason` — string shown to the user/agent explaining the decision - `updatedInput` — **rewrites the tool's arguments before execution** (powerful: a `PreToolUse` hook can sanitize commands, add flags, redirect paths, etc.) - `additionalContext` — extra context surfaced to Claude after the hook runs ## Example ```json { "hooks": { "UserPromptSubmit": [ { "matcher": "", "hooks": [ { "type": "command", "command": "echo 'Prompt submitted' >> ~/claude-log.txt" } ] } ] } } ``` ## References - Hooks docs: https://code.claude.com/docs/en/hooks - `if` field announcement: https://x.com/lydiahallie/status/2037573738670297583 ## Related - [[Claude Code]] - [[Claude Code Configuration]] - [[Claude Code Skills]] - [[Claude Code Web]]