---
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".
