--- title: "Claude Code Hooks — Automate Workflows" aliases: [hooks-guide, claude-hooks, lifecycle-hooks] tags: [claude-code, hooks, automation, lifecycle, shell, workflow] sources: [raw/Automate workflows with hooks.md] created: 2026-04-17 updated: 2026-04-17 --- ## Overview Hooks are user-defined shell commands that execute at specific points in Claude Code's lifecycle. They provide **deterministic** control — certain actions always happen, regardless of what the LLM decides to do. Four hook types exist: - `"type": "command"` — run a shell command (most common) - `"type": "http"` — POST event data to an HTTP endpoint - `"type": "prompt"` — single-turn LLM evaluation (Haiku by default) - `"type": "agent"` — multi-turn subagent with tool access (60s timeout, 50 tool turns) --- ## Hook Events Reference | Event | When it fires | |-------|--------------| | `SessionStart` | Session begins or resumes | | `UserPromptSubmit` | Prompt submitted, before Claude processes it | | `PreToolUse` | Before a tool call — can block it | | `PostToolUse` | After a tool call succeeds | | `PostToolUseFailure` | After a tool call fails | | `PermissionRequest` | Permission dialog is about to appear | | `PermissionDenied` | Tool call denied by auto-mode classifier | | `Notification` | Claude is waiting for input | | `Stop` | Claude finishes responding | | `StopFailure` | Turn ended due to API error | | `SubagentStart` / `SubagentStop` | Subagent spawned / finished | | `TaskCreated` / `TaskCompleted` | Task lifecycle events | | `ConfigChange` | Config file changed during session | | `CwdChanged` | Working directory changed | | `FileChanged` | Watched file changed on disk | | `PreCompact` / `PostCompact` | Before/after context compaction | | `InstructionsLoaded` | CLAUDE.md or rules file loaded | | `WorktreeCreate` / `WorktreeRemove` | Worktree lifecycle | | `Elicitation` / `ElicitationResult` | MCP server requesting user input | | `TeammateIdle` | Agent team member going idle | | `SessionEnd` | Session terminates | --- ## Exit Codes & Output | Exit code | Meaning | |-----------|---------| | `0` | Allow. stdout added to Claude's context (for `SessionStart`, `UserPromptSubmit`) | | `2` | Block. Write reason to stderr — Claude receives it as feedback | | Other | Allow, but log error. First line of stderr shown in transcript | **Structured JSON output** (exit 0 + JSON to stdout) for fine-grained control: ```json { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Use rg instead of grep" } } ``` `permissionDecision` values for `PreToolUse`: `"allow"` | `"deny"` | `"ask"` | `"defer"` (non-interactive only) > `"allow"` skips the interactive prompt but **does not override** deny rules from settings. --- ## Matchers Without a matcher, a hook fires on every occurrence of its event. Add `"matcher"` to narrow scope. ```json { "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "prettier --write ..." }] } ] } } ``` | Event | Matched field | Example values | |-------|--------------|----------------| | `PreToolUse`, `PostToolUse`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` | | `SessionStart` | start source | `startup`, `resume`, `clear`, `compact` | | `SessionEnd` | end reason | `clear`, `resume`, `logout`, `other` | | `ConfigChange` | config source | `user_settings`, `project_settings`, `skills` | | `FileChanged` | literal filenames | `.envrc\|.env` | | `SubagentStart/Stop` | agent type | `Bash`, `Explore`, `Plan` | **`if` field** (v2.1.85+): filter by tool name + arguments, using permission rule syntax: ```json { "type": "command", "if": "Bash(git *)", "command": "./.claude/hooks/check-git-policy.sh" } ``` Only works on tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied`). --- ## Common Recipes ### Desktop notification when Claude needs input ```json { "hooks": { "Notification": [{ "matcher": "", "hooks": [{ "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" }] }] } } ``` ### Auto-format with Prettier after file edits ```json { "hooks": { "PostToolUse": [{ "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }] }] } } ``` ### Re-inject context after compaction ```json { "hooks": { "SessionStart": [{ "matcher": "compact", "hooks": [{ "type": "command", "command": "echo 'Reminder: use Bun, not npm. Run bun test before committing.'" }] }] } } ``` ### Reload direnv when directory changes ```json { "hooks": { "CwdChanged": [{ "hooks": [{ "type": "command", "command": "direnv export bash >> \"$CLAUDE_ENV_FILE\"" }] }] } } ``` ### Auto-approve ExitPlanMode permission ```json { "hooks": { "PermissionRequest": [{ "matcher": "ExitPlanMode", "hooks": [{ "type": "command", "command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'" }] }] } } ``` --- ## Hook Scopes / Configuration Files | File | Scope | Shareable | |------|-------|-----------| | `~/.claude/settings.json` | All projects | No (local machine) | | `.claude/settings.json` | Single project | Yes (commit to repo) | | `.claude/settings.local.json` | Single project | No (gitignored) | | Managed policy settings | Organization-wide | Yes (admin-controlled) | | Plugin `hooks/hooks.json` | When plugin enabled | Yes | | Skill/agent frontmatter | While active | Yes | Disable all hooks: set `"disableAllHooks": true` in any settings file. --- ## Prompt-Based & Agent-Based Hooks **Prompt hook** — LLM evaluates a condition and returns `{"ok": true}` or `{"ok": false, "reason": "..."}`: ```json { "hooks": { "Stop": [{ "hooks": [{ "type": "prompt", "prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains\"}." }] }] } } ``` **Agent hook** — spawns a subagent that can read files and run tools before deciding: ```json { "hooks": { "Stop": [{ "hooks": [{ "type": "agent", "prompt": "Verify that all unit tests pass. Run the test suite. $ARGUMENTS", "timeout": 120 }] }] } } ``` Use prompt hooks when the event data alone is enough. Use agent hooks when you need to inspect actual codebase state. --- ## Troubleshooting | Problem | Fix | |---------|-----| | Hook not firing | Check `/hooks` menu, verify matcher case-sensitivity, confirm correct event type | | "hook error" in transcript | Test manually: `echo '{"tool_name":"Bash",...}' \| ./hook.sh; echo $?` | | `/hooks` shows nothing | Validate JSON (no trailing commas/comments), check file location | | Stop hook infinite loop | Parse `stop_hook_active` from stdin, exit 0 if `true` | | JSON validation failed | Shell profile `echo` statements pollute stdout — wrap in `if [[ $- == *i* ]]` | | Debug | Run `claude --debug-file /tmp/claude.log`, then `tail -f /tmp/claude.log` | **Permission interaction:** `PreToolUse` hooks fire before permission-mode checks. A hook returning `deny` blocks even in `bypassPermissions` mode. A hook returning `allow` cannot override deny rules from settings — hooks tighten but cannot loosen. --- ## Key Takeaways - Hooks run **deterministically** at lifecycle events — LLM cannot skip them - Use `PreToolUse` + exit 2 to block commands; use `PostToolUse` to react after - `matcher` filters by tool name (regex); `if` field filters by tool name + arguments together - Multiple hooks for the same event run **in parallel**; decisions resolve to most restrictive - `PostToolUse` cannot undo already-executed actions - `PermissionRequest` hooks don't fire in non-interactive (`-p`) mode — use `PreToolUse` instead - Hook stdout goes to Claude's context; stderr goes to Claude as error feedback (on exit 2) - Three LLM-powered types: `prompt` (single call), `agent` (multi-turn with tools), `http` (external service) --- ## Related - [[wiki/agent-sdk/overview|Agent SDK Overview]] - [[wiki/agent-sdk/agent-skills-plugins|Agent Skills & Plugins]] - [[wiki/agent-sdk/skills-in-sdk|Skills in the SDK]] - [[wiki/tech-patterns/_index|Tech Patterns]] --- ## Sources - `raw/Automate workflows with hooks.md` — clipped from https://code.claude.com/docs/en/hooks-guide