Hooks are automatic behaviors triggered at specific lifecycle points during Claude sessions. They enable powerful features like smart search routing, file conflict prevention, real-time type checking, and multi-session coordination.
Hooks run automatically at defined lifecycle events (session start, user prompt, tool use, etc.) and can:
- Inject context into Claude's awareness
- Block/redirect tool calls to more efficient alternatives
- Validate code changes in real-time
- Coordinate across concurrent sessions
- Extract learnings automatically
Hooks are implemented as command-line scripts (TypeScript, Python, shell) that receive JSON input via stdin and return JSON output via stdout.
Hooks can be registered for these lifecycle events:
Triggered when a new session begins or a session is resumed.
- Input:
{ session_id, hook_event_name, source, cwd } - Source values:
startup,resume,clear,compact
Triggered when the user submits a prompt (before Claude responds).
- Input:
{ session_id, hook_event_name, prompt, cwd } - Use cases: Skill activation suggestions, memory awareness, pattern inference
Triggered before a tool is executed. Can block, allow, modify, or ask for permission.
- Input:
{ session_id, tool_name, tool_input, tool_use_id, cwd } - Output:
{ hookSpecificOutput: { permissionDecision: 'allow'|'deny'|'ask', permissionDecisionReason, updatedInput } } - Use cases: TLDR enforcement, search routing, file claims, signature injection
- Note: Use
updatedInputto modify the tool's input before execution
Triggered after a tool executes successfully.
- Input:
{ session_id, tool_name, tool_input, tool_response, cwd } - Use cases: Type checking, handoff indexing, learning extraction
Triggered before context window compaction.
- Input:
{ session_id, transcript_path } - Use cases: Save state before compaction
Triggered when a subagent (Task tool) spawns.
- Input:
{ session_id, agent_id, hook_event_name, cwd } - Use cases: Pattern-aware coordination, agent registration
- Cannot block: Can only inject context
Triggered when a subagent completes.
- Input:
{ session_id, agent_id, hook_event_name } - Use cases: Learning extraction, pattern completion tracking
Triggered when Claude generates a stop sequence (task completion).
- Input:
{ session_id, transcript_path, stop_hook_active } - Use cases: Auto-handoff creation, force continuation
- CRITICAL: Check
stop_hook_active: trueto prevent infinite loops!
Triggered when the session terminates (clear, logout).
- Input:
{ session_id, transcript_path, reason } - Use cases: Cleanup, outcome tracking, learning extraction
Triggered when a permission dialog would be shown to the user.
- Input:
{ session_id, tool_name, tool_input, cwd } - Output:
{ hookSpecificOutput: { decision: { behavior: 'allow'|'deny', updatedInput, message, interrupt } } } - Use cases: Auto-approve trusted operations, auto-deny dangerous ones
- Requires matcher: YES (matches tool name)
Triggered when Claude Code sends a notification.
- Input:
{ session_id, message, notification_type } - Notification types:
permission_prompt,idle_prompt,auth_success,elicitation_dialog - Use cases: Custom notification handling, alerts
- Requires matcher: YES (matches notification_type)
session-register (SessionStart)
- Registers session in PostgreSQL coordination database
- Displays active peer sessions working on the same project
- Enables cross-session file conflict warnings
session-start-recall (SessionStart)
- Queries semantic memory for relevant learnings from past sessions
- Injects top 3 learnings related to current work context
- Searches ledgers and handoffs to determine context query
session-end-cleanup (SessionEnd)
- Updates continuity ledger timestamps
- Cleans up old agent cache files (7-day retention)
- Triggers background learning extraction via Braintrust
session-outcome (SessionEnd)
- Prompts user to mark handoff outcome (SUCCEEDED, PARTIAL_PLUS, PARTIAL_MINUS, FAILED)
- Provides SQLite query to find handoff ID
- Only prompts on user-initiated session end, not auto-compaction
skill-activation-prompt (UserPromptSubmit)
- Matches user prompt against skill-rules.json patterns
- Suggests relevant skills based on priority (critical, high, medium, low)
- Runs pattern inference for agentic workflows (swarm, hierarchical, pipeline, etc.)
- Shows context warnings (context percentage, resource limits)
- Can block response if critical skills are required
memory-awareness (UserPromptSubmit)
- Extracts intent from user prompt (removes meta-language)
- Fast text search against archival_memory database
- Injects MEMORY MATCH context if relevant learnings found
- Claude proactively discloses and uses memories
premortem-suggest (UserPromptSubmit)
- Suggests running premortem analysis for complex tasks
- Identifies potential failure modes before implementation
tldr-read-enforcer (PreToolUse:Read)
- Blocks Read tool for code files, returns TLDR structured context instead
- 95% token savings (50-500 tokens vs 3000-20000 raw file)
- Context-aware layers (AST, call graph, CFG, DFG, PDG) based on search intent
- Analyzes transcript and search context to determine relevant layers
- Bypasses for config files, test files, hooks/skills directories
smart-search-router (PreToolUse:Grep)
- Classifies queries as structural, semantic, or literal
- Blocks Grep, suggests TLDR search (finds + enriches in one call)
- Stores search context for downstream hooks (tldr-read-enforcer)
- Uses symbol index to detect function/class/variable targets
- Provides cross-file caller information
file-claims (PreToolUse:Edit)
- Checks PostgreSQL coordination database for file claims
- Warns if file is being edited by another concurrent session
- Claims file for current session to prevent conflicts
- Part of multi-session coordination layer
signature-helper (PreToolUse:Edit)
- Extracts function calls from edit content
- Looks up function signatures from symbol index
- Injects signatures as additional context
- Helps Claude use correct parameters without reading definition files
import-validator (PostToolUse:Edit, PostToolUse:Write)
- Validates import statements in edited files
- Checks for missing or incorrect imports
- Suggests corrections
typescript-preflight (PostToolUse:Edit, PostToolUse:Write)
- Runs tsc + qlty after TypeScript file edits
- Returns type errors and lint issues immediately
- Blocks with error message so Claude can fix before proceeding
- Skips node_modules and test files
compiler-in-the-loop (PostToolUse, Stop)
- Runs language-specific compiler/linter after code changes
- Supports Python, TypeScript, Go, Rust
- Injects errors into context for iterative fixing
subagent-start (SubagentStart)
- Pattern-aware router for multi-agent patterns
- Injects pattern-specific context (swarm, jury, pipeline, hierarchical, etc.)
- Registers agent in PostgreSQL coordination database
- Provides role-based instructions (coordinator, worker, juror, mapper, reducer)
subagent-stop (SubagentStop)
- Handles pattern-specific completion tracking
- Marks agent as completed in coordination database
- Triggers aggregation for patterns like map-reduce, jury voting
subagent-learning (SubagentStop)
- Extracts learnings from subagent transcripts
- Stores in semantic memory for future recall
- Fire-and-forget background process
Hooks are registered in .claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-register.sh",
"timeout": 10
}
]
}
],
"PreToolUse": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/tldr-read-enforcer.sh",
"timeout": 20
}
]
}
]
}
}- matcher: Tool name pattern to match (e.g., "Read", "Edit|Write", "*")
- type: Always "command" for external scripts
- command: Path to hook script (use
$CLAUDE_PROJECT_DIRor$HOMEfor portability) - timeout: Max execution time in seconds
| Pattern | Matches | Example |
|---|---|---|
Bash |
Exact match | Only Bash tool |
Edit|Write |
OR operator | Edit OR Write |
Read.* |
Regex | Read, ReadFile, etc. |
mcp__.*__write.* |
MCP tools | MCP write operations |
* |
Wildcard | All tools |
Case-sensitive: Bash does NOT match bash
| Event | Requires Matcher | Matcher Values |
|---|---|---|
| PreToolUse | YES | Tool name |
| PostToolUse | YES | Tool name |
| PermissionRequest | YES | Tool name |
| Notification | YES (optional) | permission_prompt, idle_prompt, etc. |
| SessionStart | YES (optional) | startup, resume, clear, compact |
| PreCompact | YES (optional) | manual, auto |
| UserPromptSubmit | NO | — |
| SessionEnd | NO | — |
| Stop | NO | — |
| SubagentStop | NO | — |
Hooks execute in the order listed. For PreToolUse, if any hook returns deny, subsequent hooks are skipped.
| Exit Code | Behavior | stdout | stderr |
|---|---|---|---|
| 0 | Success | JSON processed (or plain text for UserPromptSubmit/SessionStart) | Ignored |
| 2 | Blocking error | IGNORED | Error message shown |
| Other | Non-blocking error | Ignored | Shown in verbose mode |
| Hook Event | Effect of Exit Code 2 |
|---|---|
| PreToolUse | Blocks tool, stderr shown to Claude |
| PermissionRequest | Denies permission, stderr shown to Claude |
| PostToolUse | stderr shown to Claude (tool already ran) |
| UserPromptSubmit | Blocks prompt, erases it, stderr shown to user only |
| Stop | Blocks stoppage, stderr shown to Claude |
| SubagentStop | Blocks stoppage, stderr shown to Claude subagent |
| Notification | stderr shown to user only |
| PreCompact | stderr shown to user only |
| SessionStart | stderr shown to user only |
| SessionEnd | stderr shown to user only |
Execute bash commands or scripts. This is the default and most common type.
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh",
"timeout": 60
}Use an LLM (Haiku) to make context-aware decisions. Best for Stop and SubagentStop hooks.
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop. Context: $ARGUMENTS. Check if all tasks are complete.",
"timeout": 30
}Response schema:
{
"decision": "approve" | "block",
"reason": "Explanation for the decision",
"continue": false,
"stopReason": "Message shown to user",
"systemMessage": "Warning or context"
}When to use:
- Command hooks: Deterministic rules, fast execution
- Prompt hooks: Context-aware decisions, natural language understanding
MCP tools follow the naming pattern mcp__<server>__<tool>:
| Example | Description |
|---|---|
mcp__memory__create_entities |
Memory server's create entities tool |
mcp__filesystem__read_file |
Filesystem server's read file tool |
mcp__github__search_repositories |
GitHub server's search tool |
Matcher examples:
{
"matcher": "mcp__memory__.*", // All memory server tools
"matcher": "mcp__.*__write.*", // All MCP write operations
"matcher": "mcp__github__.*" // All GitHub tools
}Hooks receive JSON via stdin and output JSON via stdout.
Input Schema (varies by event type):
interface SessionStartInput {
session_id: string;
hook_event_name: string;
source: 'startup' | 'resume' | 'clear' | 'compact';
cwd: string;
}
interface PreToolUseInput {
session_id: string;
hook_event_name: string;
tool_name: string;
tool_input: Record<string, any>;
cwd: string;
transcript_path?: string;
}
interface UserPromptSubmitInput {
session_id: string;
hook_event_name: string;
prompt: string;
cwd: string;
}Output Schema:
interface HookOutput {
result?: 'continue' | 'block';
message?: string; // Injected into Claude's context
hookSpecificOutput?: {
hookEventName: string;
permissionDecision?: 'allow' | 'deny' | 'ask';
permissionDecisionReason?: string;
additionalContext?: string;
};
}TypeScript:
import { readFileSync } from 'fs';
interface UserPromptSubmitInput {
prompt: string;
}
async function main() {
const input: UserPromptSubmitInput = JSON.parse(
readFileSync(0, 'utf-8')
);
if (input.prompt.includes('delete')) {
console.log(JSON.stringify({
message: 'Reminder: Consider backing up before deletion.'
}));
} else {
console.log('{}');
}
}
main();Python:
import json
import sys
def main():
input_data = json.loads(sys.stdin.read())
if 'delete' in input_data.get('prompt', '').lower():
output = {
'message': 'Reminder: Consider backing up before deletion.'
}
print(json.dumps(output))
else:
print('{}')
if __name__ == '__main__':
main()Block dangerous operations:
import { readFileSync } from 'fs';
async function main() {
const input = JSON.parse(readFileSync(0, 'utf-8'));
if (input.tool_name === 'Bash' &&
input.tool_input.command?.includes('rm -rf /')) {
console.log(JSON.stringify({
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: 'Dangerous command blocked for safety.'
}
}));
} else {
console.log('{}');
}
}
main();- Fail Gracefully: Always output valid JSON, even on errors. Use
{}for no-op. - Timeout Awareness: Keep execution under timeout limit. Use async spawn for slow tasks.
- Silent Failures: Log errors to stderr, not stdout (stdout is parsed as JSON).
- Idempotency: Hooks may run multiple times. Design for idempotent behavior.
- Context Injection: Use
messagefor user-visible output,additionalContextfor Claude-only context. - Token Efficiency: Keep injected context concise. TLDR hooks save 95% tokens vs raw files.
When Claude tries to read a code file:
Read → tldr-read-enforcer hook intercepts
→ Analyzes search context (from smart-search-router)
→ Returns structured context (L1:AST + L2:CallGraph)
→ Claude receives function signatures + call graph (500 tokens)
→ vs raw file read (5000 tokens)
→ 90% token savings
When Claude tries to grep:
Grep "process_data" → smart-search-router hook intercepts
→ Classifies as "literal" query
→ Extracts target: "process_data" (function)
→ Stores search context for tldr-read-enforcer
→ Blocks Grep, suggests TLDR search instead
→ TLDR finds + enriches (call graph, docstring)
When Claude tries to edit a file:
Edit file.py → file-claims hook intercepts
→ Checks PostgreSQL for file claim
→ Session A already editing file.py
→ Warns: "File conflict: Session A is editing file.py"
→ Claude can coordinate or edit different file
When Claude edits a TypeScript file:
Edit hook.ts → typescript-preflight hook runs after edit
→ Executes: tsc --noEmit hook.ts
→ Finds: "Type 'string' not assignable to 'number'"
→ Blocks with error message
→ Claude sees error immediately and fixes in next turn
When user submits a prompt:
Prompt: "refactor this code" → skill-activation-prompt hook runs
→ Matches "refactor" keyword
→ Suggests: refactor skill (high priority)
→ Suggests: premortem skill (medium priority)
→ Claude: /refactor before responding
Subagent hooks detect multi-agent patterns from environment variables:
PATTERN_TYPE=swarm # Enables broadcast messaging
PATTERN_TYPE=jury # Enables vote isolation
PATTERN_TYPE=pipeline # Enables stage sequencingHooks inject pattern-specific context and coordinate agent interactions.
The session-symbol-index hook builds a symbol index at session start:
{
"process_data": {
"type": "function",
"location": "/path/to/file.py:42"
},
"DataProcessor": {
"type": "class",
"location": "/path/to/file.py:10"
}
}Used by smart-search-router and signature-helper for accurate code understanding.
smart-search-router stores search context that tldr-read-enforcer consumes:
{
"timestamp": 1704067200000,
"queryType": "literal",
"pattern": "process_data",
"target": "process_data",
"targetType": "function",
"suggestedLayers": ["ast", "call_graph", "cfg"],
"callers": ["file1.py:10", "file2.py:25"]
}This enables multi-layer context enrichment without repeated tool calls.
session-end-cleanup spawns background process to extract learnings:
uv run python scripts/braintrust_analyze.py --learn --session-id <id>Uses LLM-as-judge to extract:
- What worked
- What failed
- Decisions made
- Patterns discovered
Stored in archival_memory for future semantic recall.
Hooks log to stderr (not stdout, which is parsed as JSON):
console.error('[my-hook] Processing input:', input);echo '{"session_id":"test","prompt":"delete all files"}' | \
node .claude/hooks/src/my-hook.jsComment out in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
// {
// "matcher": "Read",
// "hooks": [...]
// }
]
}
}If hook exceeds timeout, it's killed and Claude continues. Increase timeout if needed:
{
"timeout": 30 // seconds
}- TLDR hooks: 95% token savings (50-500 tokens vs 3000-20000 raw)
- Search routing: Prevents inefficient Grep scans
- Signature injection: Avoids reading definition files (saves 1000+ tokens per function)
- Symbol indexing: One-time 5s cost at session start, saves 100+ tool calls
- Learning extraction: Background process, doesn't block session end
Hooks run with the same permissions as the Claude CLI. They can:
- Execute arbitrary commands
- Read/write files in the project
- Access environment variables
- Make network requests
Recommendations:
- Review hook source code before enabling
- Use
$CLAUDE_PROJECT_DIRpaths to scope to current project - Set reasonable timeouts to prevent hanging
- Validate hook inputs (untrusted user prompts)
- Use readonly operations when possible (PreToolUse hooks)
Hook not running:
- Check settings.json syntax (valid JSON)
- Verify hook script exists at specified path
- Check file permissions (executable)
- Look for stderr logs
Hook timing out:
- Increase timeout value
- Move slow operations to background spawn
- Use caching for expensive operations
Hook output not appearing:
- Verify JSON output format
- Check for stdout vs stderr confusion
- Ensure
messagefield is set for user-visible output
Type errors after hook runs:
- typescript-preflight may be reporting legitimate errors
- Check hook output for error details
- Fix reported type errors before proceeding
Token savings (actual metrics):
- TLDR enforcement: 95% reduction (5000 → 250 tokens)
- Smart search routing: 90% reduction (2000 → 200 tokens)
- Signature injection: 85% reduction (1000 → 150 tokens)
Coordination (actual use):
- File claims prevented 12 conflicts across 3 concurrent sessions
- Session awareness showed 2 other active sessions on same project
- Learning recall surfaced 5 relevant past solutions
Validation (actual catches):
- typescript-preflight caught 47 type errors before commit
- import-validator fixed 23 import paths
- compiler-in-the-loop prevented 8 runtime errors
- ARCHITECTURE.md - Overall system design
- QUICKSTART.md - Getting started
.claude/hooks/src/- Hook source code.claude/settings.json- Hook configuration