Claude Code provides lifecycle hooks for various events during session execution, tool usage, and system operations.
Session Lifecycle
SessionStart
Runs when a new session starts or resumes.
Timing: After session initialization, before first user message
Configuration:
{
"hooks" : {
"SessionStart" : [
{
"hooks" : [
{
"type" : "command" ,
"command" : "bash ${CLAUDE_PLUGIN_ROOT}/setup-session.sh"
}
]
}
]
}
}
Input:
{
"session_id" : "abc123" ,
"working_directory" : "/path/to/project" ,
"is_resume" : false
}
Use cases:
Environment configuration
Loading project-specific context
Initializing external services
Setting up Docker containers
Loading API credentials
SessionStart hooks are deferred during startup to reduce time-to-interactive by ~500ms.
Example:
#!/bin/bash
# Configure environment for Claude session
export PROJECT_ENV = "development"
source .env.local 2> /dev/null || true
# Start development services
docker-compose up -d db redis
echo "Session environment configured" >&2
exit 0
Stop
Runs when a session is ending (user exit or interruption).
Timing: Before session cleanup and exit
Configuration:
{
"hooks" : {
"Stop" : [
{
"hooks" : [
{
"type" : "command" ,
"command" : "${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh"
}
]
}
]
}
}
Input:
{
"session_id" : "abc123" ,
"transcript_path" : "/path/to/transcript.jsonl" ,
"last_assistant_message" : "Final message from Claude" ,
"working_directory" : "/path/to/project"
}
Output (optional):
{
"decision" : "block" ,
"reason" : "Continue with new prompt" ,
"systemMessage" : "Loop iteration 5"
}
Stop hooks can block session exit by returning "decision": "block". This enables continuous loop patterns.
Use cases:
Cleanup operations
Saving session state
Stopping services
Creating session summaries
Continuous loop workflows (Ralph Wiggum pattern)
Example (Ralph Wiggum Loop):
#!/bin/bash
# Prevent exit and continue loop
set -euo pipefail
# Read hook input
HOOK_INPUT = $( cat )
# Check if loop is active
if [[ ! -f ".claude/ralph-loop.local.md" ]]; then
exit 0 # Allow exit
fi
# Parse state
FRONTMATTER = $( sed -n '/^---$/,/^---$/{ /^---$/d; p; }' ".claude/ralph-loop.local.md" )
ITERATION = $( echo " $FRONTMATTER " | grep '^iteration:' | sed 's/iteration: *//' )
MAX_ITERATIONS = $( echo " $FRONTMATTER " | grep '^max_iterations:' | sed 's/max_iterations: *//' )
# Check if max reached
if [[ $MAX_ITERATIONS -gt 0 ]] && [[ $ITERATION -ge $MAX_ITERATIONS ]]; then
rm ".claude/ralph-loop.local.md"
exit 0 # Allow exit
fi
# Continue loop
NEXT_ITERATION = $(( ITERATION + 1 ))
PROMPT_TEXT = $( awk '/^---$/{i++; next} i>=2' ".claude/ralph-loop.local.md" )
# Update state
sed "s/^iteration: .*/iteration: $NEXT_ITERATION /" ".claude/ralph-loop.local.md" > tmp
mv tmp ".claude/ralph-loop.local.md"
# Block exit and feed prompt back
jq -n \
--arg prompt " $PROMPT_TEXT " \
--arg msg "🔄 Ralph iteration $NEXT_ITERATION " \
'{
"decision": "block",
"reason": $prompt,
"systemMessage": $msg
}'
exit 0
Setup
Runs for repository setup and maintenance operations.
Timing: When invoked via CLI flags: --init, --init-only, or --maintenance
Configuration:
{
"hooks" : {
"Setup" : [
{
"hooks" : [
{
"type" : "command" ,
"command" : "bash ./scripts/setup.sh"
}
]
}
]
}
}
Input:
{
"mode" : "init" ,
"working_directory" : "/path/to/project"
}
Use cases:
Repository initialization
Dependency installation
Database migrations
Environment setup
Periodic maintenance tasks
Example:
# Run setup on new clone
claude --init
# Run setup without starting session
claude --init-only
# Run maintenance tasks
claude --maintenance
Runs before a tool is executed.
Timing: After Claude requests tool use, before execution
Configuration:
{
"hooks" : {
"PreToolUse" : [
{
"matcher" : "Bash" ,
"hooks" : [
{
"type" : "command" ,
"command" : "python3 /path/to/validator.py"
}
]
}
]
}
}
Input:
{
"session_id" : "abc123" ,
"tool_name" : "Bash" ,
"tool_input" : {
"command" : "git push origin main" ,
"workdir" : "/path/to/project"
}
}
Output:
{
"decision" : "block" ,
"additionalContext" : "This command requires manual approval"
}
Exit codes:
0 - Allow execution
1 - Show stderr to user only (allow execution)
2 - Block execution, show stderr to Claude
Use cases:
Command validation
Security checks
Command transformation
Rate limiting
Audit logging
Example (Command Validator):
#!/usr/bin/env python3
import json
import sys
import re
VALIDATION_RULES = [
(
r " ^ grep \b (?! . * \| ) " ,
"Use 'rg' (ripgrep) instead of 'grep' for better performance" ,
),
(
r " ^ find \s + \S + \s + -name \b " ,
"Use 'rg --files -g pattern' instead of 'find -name'" ,
),
]
def validate_command ( command : str ) -> list[ str ]:
issues = []
for pattern, message in VALIDATION_RULES :
if re.search(pattern, command):
issues.append(message)
return issues
try :
data = json.load(sys.stdin)
except json.JSONDecodeError as e:
print ( f "Error: Invalid JSON: { e } " , file = sys.stderr)
sys.exit( 1 )
tool_name = data.get( "tool_name" , "" )
if tool_name != "Bash" :
sys.exit( 0 )
command = data.get( "tool_input" , {}).get( "command" , "" )
if not command:
sys.exit( 0 )
issues = validate_command(command)
if issues:
for msg in issues:
print ( f "• { msg } " , file = sys.stderr)
sys.exit( 2 ) # Block execution
sys.exit( 0 ) # Allow execution
PostToolUse
Runs after a tool completes execution.
Timing: After tool execution, before result is sent to Claude
Configuration:
{
"hooks" : {
"PostToolUse" : [
{
"matcher" : "Bash" ,
"hooks" : [
{
"type" : "command" ,
"command" : "python3 /path/to/logger.py"
}
]
}
]
}
}
Input:
{
"session_id" : "abc123" ,
"tool_name" : "Bash" ,
"tool_input" : {
"command" : "npm test"
},
"tool_output" : {
"stdout" : "All tests passed" ,
"stderr" : "" ,
"exit_code" : 0
}
}
Use cases:
Result logging
Metrics collection
Notification triggers
Result transformation
Error analysis
Git Worktree Lifecycle
WorktreeCreate
Runs when a git worktree is created for agent isolation.
Timing: After worktree creation, before agent starts
Configuration:
{
"hooks" : {
"WorktreeCreate" : [
{
"hooks" : [
{
"type" : "command" ,
"command" : "bash ./scripts/setup-worktree.sh"
}
]
}
]
}
}
Input:
{
"worktree_path" : "/path/to/worktree" ,
"branch_name" : "claude/task-123" ,
"base_branch" : "main"
}
Use cases:
Worktree-specific configuration
Installing dependencies
Database setup
Environment configuration
WorktreeRemove
Runs when a git worktree is being removed.
Timing: Before worktree deletion
Configuration:
{
"hooks" : {
"WorktreeRemove" : [
{
"hooks" : [
{
"type" : "command" ,
"command" : "bash ./scripts/cleanup-worktree.sh"
}
]
}
]
}
}
Input:
{
"worktree_path" : "/path/to/worktree" ,
"branch_name" : "claude/task-123"
}
Use cases:
Cleanup operations
Stopping services
Collecting artifacts
Backup operations
Configuration Lifecycle
ConfigChange
Runs when configuration files change during a session.
Timing: When settings.json or related config files are modified
Configuration:
{
"hooks" : {
"ConfigChange" : [
{
"hooks" : [
{
"type" : "command" ,
"command" : "python3 /path/to/audit-config.py"
}
]
}
]
}
}
Input:
{
"session_id" : "abc123" ,
"config_file" : "/path/to/settings.json" ,
"changes" : {
"permissions.allow" : [ "Bash" ]
}
}
Output (optional):
{
"decision" : "block" ,
"reason" : "Configuration changes require administrator approval"
}
Use cases:
Enterprise security auditing
Configuration validation
Change approval workflows
Compliance enforcement
Agent Team Lifecycle
TeammateIdle
Runs when an agent teammate becomes idle.
Timing: When teammate completes work and awaits instructions
Configuration:
{
"hooks" : {
"TeammateIdle" : [
{
"hooks" : [
{
"type" : "command" ,
"command" : "python3 /path/to/notify.py"
}
]
}
]
}
}
Input:
{
"session_id" : "abc123" ,
"agent_id" : "teammate-1" ,
"agent_type" : "code-reviewer"
}
TaskCompleted
Runs when a background task completes.
Timing: When task finishes successfully or with error
Configuration:
{
"hooks" : {
"TaskCompleted" : [
{
"hooks" : [
{
"type" : "command" ,
"command" : "python3 /path/to/task-logger.py"
}
]
}
]
}
}
Input:
{
"session_id" : "abc123" ,
"task_id" : "task-1" ,
"status" : "success" ,
"result" : "Task completed successfully" ,
"duration_ms" : 5432
}
SubagentStop
Runs when a subagent session ends.
Timing: After subagent completes, before returning to parent
Input:
{
"session_id" : "abc123" ,
"parent_session_id" : "parent-123" ,
"agent_type" : "code-analyzer" ,
"last_assistant_message" : "Analysis complete"
}
Best Practices
Hooks run synchronously and block execution:
Target < 100ms execution time
Use background processes for slow work
Cache expensive operations
Avoid network calls when possible
try :
data = json.load(sys.stdin)
# Process data
except Exception as e:
print ( f "Hook error: { e } " , file = sys.stderr)
sys.exit( 1 ) # Show error to user, allow execution
Use appropriate exit codes
0 - Success, allow operation
1 - Non-blocking error (user sees stderr)
2 - Blocking error (Claude sees stderr)
Choose based on whether Claude should know about the issue
# Store session-specific state
STATE_FILE = f "~/.claude/hook-state- { session_id } .json"
# Clean up old state files
cleanup_old_states() # Runs periodically
Next Steps
Hook Examples See real-world hook implementations and patterns