--- name: hooks-pattern description: When to choose hooks over prompts or subagents, and common hook shapes that work. layer: pattern cc_feature: hooks source: https://docs.claude.com/en/docs/claude-code/hooks concept: hooks-decision-and-shapes last_verified: 2026-04-18 ngram_overlap_score: null review_status: approved --- # Hooks — Pattern ## When to reach for a hook Use hooks when the behavior must hold even if Claude is prompt-injected, distracted, or adversarial. Hooks are outside the model's control loop — Claude cannot talk its way past them. Good fits: - **Hard prohibitions** — "never run `rm -rf /`", "never push to main without a commit signature", "never read files outside this repo". - **Deterministic context injection** — always show git status at session start; always inject the current sprint's tasks. - **Audit trails** — log every Bash call, every file write, outside the conversation context so it survives `/clear`. - **Compliance boundaries** — redact secrets from transcripts; block tool calls that would leak PII. Bad fits: - Behavior that requires judgment ("should this test run?") — that's a subagent call. - Heuristics that drift ("mostly block, sometimes allow") — a hook that frequently second-guesses itself is a sign the rule belongs in a prompt or skill. - Anything that needs to read lots of conversation history — hooks see a payload, not the full context. ## Common shapes ### Shape A: PreToolUse deny A small script that reads `tool_input`, matches a pattern, and exits with a `block` decision. Latency: a few ms. Used for command denylists, path guards, secrets scanners. ### Shape B: UserPromptSubmit context injection A script that reads the prompt, computes context (e.g., recent git log, active TODO list), and emits JSON with an `additionalContext` field. The harness adds that context to the prompt before Claude sees it. ### Shape C: Stop hook with reminder A script that runs when Claude finishes a turn. Checks for uncommitted work, surfaces it as a notification. Non-blocking. ### Shape D: SessionStart status Runs once per session. Prints repo metadata (branch, open PRs, recent commits) so every new session starts with shared context. ## Pitfalls - **Slow hooks compound.** A 500 ms PreToolUse hook run 50 times per session adds 25 seconds of latency. - **Hooks without error handling crash the turn.** A hook that exits non-zero on an edge case blocks real work. Default to exit 0 with a logged warning. - **Shell-injection in hook scripts.** A hook that interpolates `tool_input.command` into `bash -c` is itself a security hole. Parse inputs; never interpolate unescaped. - **Hooks can leak secrets into transcripts** if their output mentions env-var values. Scrub before emitting. - **Cross-platform scripts.** A hook that assumes bash 4 or GNU sed breaks on macOS. Prefer Node.js, Python, or POSIX sh. ## Composition with other features - Hooks + subagents: a hook can delegate the "should this be blocked" question to a subagent when the decision needs judgment. Cost: a full model call per hook invocation — use sparingly. - Hooks + MCP: a hook can call out to an MCP-exposed tool for policy lookup. Latency depends on transport. - Hooks + plan mode: hooks fire during plan mode too. Useful for enforcing "no writes while planning".