Claude Code Hook Capabilities
Reference for what hooks can do in Claude Code.
Why Hooks Matter: The Only Guarantee
Hook = 100% execution guarantee (event-based)
Skill/Agent/MCP = ~20-80% (Claude's judgment)
Key insight: Hooks are the ONLY mechanism that executes without Claude's decision.
See orchestration-patterns.md for forcing skill/agent activation.
5 Hook Roles
| Role | Description | Examples |
|---|
| Gate | Block/allow tool execution | Prevent dangerous commands, workflow precondition checks |
| Side Effect | Auto-actions after tool execution | Formatters, linters, auto-commit |
| State Manager | Workflow state management | State file creation/deletion, phase tracking |
| External Integrator | External system integration | MCP calls, HTTP API, WebSocket, Slack |
| Context Injector | Session context injection | Load project settings, activate services |
Event Types and Characteristics
| Event | Block | Special Features | Verification |
|---|
| SessionStart | No | source (compact/new) | Verified |
| UserPromptSubmit | Yes | stdout auto-injects into Claude context | Verified |
| PreToolUse | Yes | updatedInput modifies input, tool_use_id | Verified |
| PermissionRequest | Yes | allow/deny/ask + input modification | Unverified |
| PostToolUse | No | tool_response (access results) | Verified |
| Stop | Yes | stop_hook_active (loop prevention) | Verified |
| SubagentStop | Yes | parent-child correlation via tool_use_id | Unverified |
| Notification | No | includes notification_type | Verified |
| PreCompact | No | trigger (auto/manual) | Verified |
| SessionEnd | No | On session end | Unverified |
22 Universal Approaches
Control Patterns
| Approach | Description | Event |
|---|
| Iteration Control | Track iteration count + max limit | Stop |
| Force Continuation | Use exit 2 to continue Claude work | Stop |
| Promise Detection | Detect Claude response patterns, conditional exit | Stop |
| Infinite Loop Prevention | Prevent recursion via parent_tool_use_id | UserPromptSubmit |
| Threshold Branching | Branch based on error/warning count | Stop |
Input Manipulation
| Approach | Description | Event |
|---|
| Input Modification | Modify tool input via updatedInput | PreToolUse, PermissionRequest |
| Path Normalization | Auto-convert relative to absolute paths | PreToolUse |
| Environment Injection | Auto-inject environment variables | PreToolUse |
| Dry-run Enforcement | Auto-add --dry-run to dangerous commands | PreToolUse |
Context Management
| Approach | Description | Event |
|---|
| Context Injection | stdout auto-injects into Claude context | UserPromptSubmit |
| Progressive Loading | Load context/skills on demand | UserPromptSubmit |
| Skill Auto-Activation | Keywords trigger skill suggestions | UserPromptSubmit |
| Transcript Parsing | Read and analyze previous responses | Stop |
| Transcript Backup | Backup session transcript | PreCompact |
State Management
| Approach | Description | Event |
|---|
| Session Cache | Accumulate per-session state + aggregate results | PostToolUse |
| Session Lifecycle | Initialize/cleanup state via SessionStart/End | SessionStart/End |
| Checkpoint Commit | Checkpoint on every change, then squash | PostToolUse, Stop |
| Session Branching | Auto-isolate Git branches per session | Pre/PostToolUse |
External Integration
| Approach | Description | Event |
|---|
| Notification Forwarding | Forward notifications to Slack/Discord/external | Notification |
| Desktop/Audio Alert | osascript, notify-send, TTS | Notification |
| Subagent Correlation | Track parent-child via tool_use_id | SubagentStop |
Security & Compliance
| Approach | Description | Event |
|---|
| Auto-Approval | Auto-approve specific tools/commands | PermissionRequest |
| Secret Scanning | Detect and block API keys/secrets | PreToolUse |
| Compliance Audit | Compliance logging + violation detection | PostToolUse |
Implementation Techniques
| Approach | Description | Event |
|---|
| TypeScript Delegation | Delegate complex logic to .ts | Any |
| Hook Chaining | Execute multiple hooks sequentially | Any |
| Background Execution | Async via run_in_background | Any |
| Argument Pattern Matching | Match arguments like Bash(npm test*)
| PreToolUse |
| MCP Tool Matching | Match MCP like mcp__memory__.*
| PreToolUse |
| Prompt-Type Hook | LLM evaluation via type: "prompt" | Any |
Capabilities vs Limitations
| Possible | Not Possible |
|---|
| File create/delete/modify | Block in PostToolUse |
| MCP/HTTP/WebSocket calls | Direct Claude context modification |
| UserPromptSubmit stdout to context | Delete existing context |
| PreToolUse/PermissionRequest input modification | Cancel already-executed tools |
| Continue work from Stop | Unlimited forcing (infinite loop risk) |
Data Passing Methods (Important)
stdin JSON (Verified)
All session/project info is passed via stdin JSON:
session_id
- Session UUID
cwd
- Project directory
transcript_path
- Session log file path
tool_use_id
- Tool call ID (PreToolUse/PostToolUse)
stdin JSON Structure by Event
# UserPromptSubmit
{"prompt": "user message", "session_id": "...", "cwd": "/path"}
# PreToolUse / PostToolUse
{"tool_name": "Bash", "tool_input": {"command": "npm test"}, "session_id": "..."}
# PermissionRequest
{"tool_name": "Bash", "tool_input": {...}, "permission_type": "execute"}
# Stop
{"stop_reason": "end_turn", "session_id": "..."}
# SubagentStop
{"agent_name": "backend-dev", "result": "...", "session_id": "..."}
Environment Variables (Verified)
CLAUDE_PROJECT_DIR
,
CLAUDE_SESSION_ID
etc. are
NOT environment variables!
Actually set environment variables:
CLAUDE_CODE_ENABLE_CFC="false"
CLAUDE_CODE_ENTRYPOINT="cli"
Settings Reload
settings.json
changes only apply in new sessions
Exit Code Reference
| Exit Code | Meaning | Behavior |
|---|
| 0 | Success/Allow | Normal proceed |
| 1 | Error | Hook failure, show warning |
| 2 | Block/Continue | Varies by event |
Exit 2 behavior by event:
| Event | exit 2 Behavior |
|---|
| PreToolUse | Block tool execution |
| PostToolUse | Ignore result (prompt retry) |
| PermissionRequest | Deny permission request |
| Stop | Force Claude to continue |
| UserPromptSubmit | Abort prompt processing |
Hook Execution Order
Multiple hooks on same event → Sequential execution (definition order)
One hook exits 2 → Subsequent hooks don't run
Timeout Setting
{"type": "command", "command": "script.sh", "timeout": 10000}
Default: 60000ms (1 minute)
Common Mistakes
| Mistake | Problem | Solution |
|---|
| Not reading stdin | Missing JSON input | INPUT=$(cat) required |
| stdout debug output | Context pollution | Use stderr (>&2 ) |
| exit 1 vs exit 2 confusion | Unintended behavior | exit 1=error, exit 2=block |
| Parsing without jq | Unstable | Install and use jq |
References