Claude Code · Complete guide
Claude Code Hooks: Complete Guide to All 30+ Lifecycle Events
Claude Code hooks fire deterministically at 30+ lifecycle points — before and after every tool call, on session start and end, when files change on disk, during context compaction. Here's the definitive map with real configs for safety enforcement, auto-formatting, and session context loading.
Every CLAUDE.md instruction you write is advisory. The model reads it, understands it, and then — after a long agentic session, with a bloated context window, chasing a hairy bug — quietly ignores it. That's not a defect. It's a property of token-prediction: the model is always trading off competing pressures, and your instructions are just one of them. Hooks are different. They are shell commands, HTTP endpoints, MCP tool calls, prompts, or subagents that Claude Code executes automatically at defined lifecycle points — before a tool runs, after a file changes, when a session starts. The model has no vote. Hooks fire every time, unconditionally.
The 2026 hooks system now covers 30+ lifecycle events across five cadences. If you've only wired up a PreToolUse check to block rm -rf, you're using maybe 5% of the surface. This guide covers all of it.
30+
lifecycle events
session · turn · tool · subagent · file
5
handler types
command · HTTP · MCP · prompt · agent
4
permission decisions
deny · allow · ask · defer
Hooks vs CLAUDE.md: determinism wins
CLAUDE.md is great for context and conventions. Hooks are for enforcement. The distinction matters because the failure modes are completely different.
When CLAUDE.md says "run npm test before committing," that works until the model is 40 tool calls deep and under token pressure. Then it summarizes the instruction away. When a PostToolUse hook runs npm test automatically after every Write, the build is always verified. No model discretion. No context window games.
Hooks are configured in settings.json at one of four scopes: user-global (~/.claude/settings.json), project (.claude/settings.json, committable), project-local (.claude/settings.local.json, gitignored), or organization-wide via managed policy.
The five event cadences
The 30+ events cluster into five cadences. Understanding which cadence you need tells you which hook to reach for.
1. Session-level
Three events fire once per session: SessionStart (when a session begins or resumes), Setup (one-time initialization with --init or --maintenance), and SessionEnd (on termination). SessionStart is where you inject environment context before the model sees any user input.
2. Per-turn
UserPromptSubmit fires before Claude processes each user message — useful for validation, enrichment, or rate-limit guards. Stop and StopFailure fire when a turn completes normally or errors out. There's also UserPromptExpansion when a slash command expands.
3. Agentic loop (per tool call)
The most powerful cadence. PreToolUse fires before any tool executes and can block it. PostToolUse fires on success; PostToolUseFailure fires on failure; PostToolBatch fires after a parallel tool batch resolves. PermissionRequest and PermissionDenied handle the permission dialog lifecycle.
4. Subagent and task events
SubagentStart / SubagentStop wrap each subagent invocation — useful for logging, rate limiting, or injecting per-subagent context. TaskCreated / TaskCompleted fire for background task management. TeammateIdle signals an agent teammate going idle in multi-agent pipelines.
5. Environment and file events
FileChanged watches specific files on disk — wire it to .env to detect secret additions in real time. CwdChanged fires on directory changes. ConfigChange fires when any settings file is modified. InstructionsLoaded fires when CLAUDE.md or rules are loaded. PreCompact / PostCompact wrap context compaction — a natural checkpoint to snapshot state or reload skills. WorktreeCreate / WorktreeRemove wrap git worktree lifecycle.
The five handler types
Once you know which event to hook, you pick a handler type. Each has different capabilities and tradeoffs.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/check-command.sh",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write ${tool_input.file_path}"
}
]
}
]
}
}Command hooks run shell scripts that receive a JSON payload on stdin and return decisions via exit code and stdout. They are the most flexible handler — full shell access, pipes, environment variables. HTTP hooks POST JSON to a local or remote endpoint and parse the response as a decision; useful when you want hooks to call your own services (audit logging, policy engines). MCP tool hooks call a tool on any connected MCP server — the same JSON decision schema, but the MCP server owns the logic. Prompt hooks send the event payload to a Claude model for a yes/no evaluation, returning a JSON decision; the cost is latency but you get natural-language policy evaluation. Agent hooks spawn a subagent with Read, Grep, and Glob access to verify conditions — the nuclear option for complex pre-flight checks.
PreToolUse: blocking dangerous calls
PreToolUse is where most teams start. It's the safety layer that CLAUDE.md can never be. The hook receives the tool name and its full input, and returns one of four decisions: deny (block the call), allow (approve without user prompt), ask (surface the permission dialog), or defer (continue normal permission flow).
#!/usr/bin/env bash
# Block rm -rf and writes outside the project root.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -qE 'rm -rf|sudo rm|dd if='; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by project policy"
}
}'
exit 0
fi
exit 0 # No opinion — normal permission flowThe matcher field filters which tool names trigger the hook. Bare strings match exactly ("Bash"); pipe-separated strings match any of the list ("Write|Edit"); any other pattern is treated as a JavaScript regex. MCP tools follow the mcp__server__tool naming convention so mcp__memory__.* matches every tool on the memory server.
PostToolUse: auto-lint and context injection
PostToolUse is the automation hook. It fires after every successful tool call, giving you a guaranteed post-processing window before Claude sees the result.
The most common pattern: run a formatter after every Write or Edit. This means Claude never has to remember to run Prettier — the file is formatted before it even returns from the tool call.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/post-write.mjs"
}
]
}
]
}
}#!/usr/bin/env node
// Run formatter then return additionalContext with lint status.
import { execSync } from "node:child_process";
const input = JSON.parse(await readStdin());
const filePath = input.tool_input?.file_path;
let lintStatus = "skipped";
if (filePath?.match(/\.tsx?$/)) {
try {
execSync(`npx prettier --write ${filePath}`, { stdio: "pipe" });
execSync(`npx eslint ${filePath} --max-warnings 0`, { stdio: "pipe" });
lintStatus = "passed";
} catch {
lintStatus = "failed — run: npx eslint " + filePath;
}
}
process.stdout.write(JSON.stringify({
hookSpecificOutput: {
hookEventName: "PostToolUse",
additionalContext: `Lint: ${lintStatus}`,
},
}));
async function readStdin() {
const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
return Buffer.concat(chunks).toString();
}The additionalContext field is the other superpower of PostToolUse. Whatever string you put there gets injected back into the model's context as a system message — so Claude can read "Lint: failed — run: npx eslint src/auth.ts" and immediately act on it, without you having to paste the output manually.
SessionStart: loading skills on boot
SessionStart fires once when a session begins or resumes, before the model processes any user input. The hook can return an additionalContext string that gets injected as the first system message — effectively a dynamic CLAUDE.md that runs fresh code every session.
#!/usr/bin/env bash
# Inject branch, environment, and installed skill list at session start.
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
ENV_FILE=".env"
SKILL_LIST=$(ls .claude/skills/ 2>/dev/null | tr '\n' ', ' | sed 's/, $//')
jq -n --arg ctx "Active branch: $BRANCH
Environment: $([ -f "$ENV_FILE" ] && echo 'found' || echo 'missing — copy .env.example')
Installed skills: ${SKILL_LIST:-none}
Run /hooks to see all active lifecycle hooks." '{
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: $ctx,
sessionTitle: ("session on " + $ctx)
}
}'The reloadSkills: true flag in the SessionStart response tells Claude Code to re-read all SKILL.md files from disk before proceeding — useful in monorepos where skills are updated frequently and you want fresh instructions without restarting.
100%
hook execution rate
Unlike CLAUDE.md instructions, hooks fire every time — no model discretion, no context pressure, no exceptions.
Composing hooks with skills-hub.ai
Hooks and skills are designed to work together. A SKILL.md file can declare hooks in its frontmatter — when a subagent loads the skill, the hooks activate for the lifetime of that subagent. The hooks deactivate when the subagent terminates.
---
name: safe-bash
description: Wraps all Bash tool calls with a destructive-command blocker.
version: 1.0.0
category: security
platforms:
- CLAUDE_CODE
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: ".claude/hooks/check-command.sh"
PostToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: "npx prettier --write ${tool_input.file_path}"
---
When executing shell commands, always explain what the command does
before running it. Prefer targeted file edits over sed pipelines.On skills-hub.ai, the productivity category contains pre-built hook configurations for common patterns: the claude-code-hooks-setup skill installs a production-grade hook suite in one command, covering safety enforcement, auto-formatting, session context loading, and build gates.
# Install the hooks-setup skill and configure it for your stack
npx @skills-hub-ai/cli install claude-code-hooks-setup
# Or browse the full hooks and safety catalog
npx @skills-hub-ai/cli search hooksYou can also run /hooks inside any Claude Code session to open a read-only browser showing every configured hook with its event, matcher, handler type, source file, and details. It's the fastest way to audit what's wired up — and to spot hooks that are firing when you don't expect them to.
Unlike CLAUDE.md instructions which are advisory and may be ignored during long sessions, hooks are deterministic. They execute every time, guaranteed.
The canonical reading order if you're new to this: start with PreToolUse on Bash (the safety net every project needs), then add PostToolUse on Write/Edit (the formatter that eliminates an entire class of review comments), then configure SessionStart once you have repeatable context you want injected every session. The rest of the 30+ events are there when you need them — FileChanged for secret detection, PreCompact for state snapshots, SubagentStart for per-agent rate limiting — but those three cover 90% of real-world hook value.
Related reading: the subagents guide for how hooks compose with subagent pipelines, dynamic workflows for the MessageDisplay hook and hot-reloadable skills, and the hook glossary entry for a quick-reference definition.
Written by
Skills-Hub Team
Anthropic ecosystem coverage
Skills-Hub is the open registry for AI coding skills, 4,400+ SKILL.md files synced daily from Anthropic, Google, Microsoft, and 90+ official sources. Free + MIT.