Skip to main content
Hooks allow you to execute custom code at specific points in Claude Code’s lifecycle, enabling powerful integrations, validation, and automation workflows.

What are Hooks?

Hooks are scripts that run automatically when specific events occur:
  • Session lifecycle events - When sessions start or stop
  • Tool execution - Before or after tools are used
  • Git worktree operations - When worktrees are created or removed
  • Configuration changes - When settings are modified
  • Agent operations - When agents become idle or tasks complete

Hook Types

Lifecycle Hooks

  • SessionStart
  • Stop
  • Setup
  • WorktreeCreate
  • WorktreeRemove

Tool Hooks

  • PreToolUse
  • PostToolUse

Configuration Hooks

  • ConfigChange

Agent Hooks

  • TeammateIdle
  • TaskCompleted
  • SubagentStop

Hook Configuration

Hooks are configured in hooks.json files:
hooks.json
{
  "description": "Security validation hooks",
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 /path/to/validator.py"
          }
        ]
      }
    ]
  }
}

Hook Locations

Hooks can be defined at multiple levels:
# Project-level hooks
project/.claude/hooks/hooks.json

# User-level hooks  
~/.claude/hooks/hooks.json

# Plugin hooks
plugins/my-plugin/hooks/hooks.json

# Managed/enterprise hooks (highest priority)
managed-hooks.json
Managed hooks have the highest priority and cannot be disabled by users when allowManagedHooksOnly is set.

Hook Input/Output

Input Format

Hooks receive JSON input via stdin:
{
  "session_id": "abc123",
  "tool_name": "Bash",
  "tool_input": {
    "command": "git push"
  },
  "transcript_path": "/path/to/transcript.jsonl",
  "last_assistant_message": "Pushing changes to remote..."
}

Output Format

Hooks can output JSON to control behavior:
{
  "decision": "block",
  "reason": "This command requires approval",
  "additionalContext": "Security policy: all git push requires manual review"
}

Exit Codes

0
Allow
Tool execution proceeds normally.
1
Error (user only)
Shows stderr to the user but not to Claude. Tool execution proceeds.
2
Block
Blocks tool execution and shows stderr to Claude. Claude can see the error and adjust.
3+
Error
Generic error. Tool execution may be blocked.

Hook Types Explained

Command Hooks

Execute shell commands:
{
  "type": "command",
  "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validator.py"
}
Available variables:
  • ${CLAUDE_PLUGIN_ROOT} - Plugin directory path
  • ${CLAUDE_SESSION_ID} - Current session ID
  • Environment variables from the session

HTTP Hooks

POST JSON to a URL and receive JSON response:
{
  "type": "http",
  "url": "https://api.example.com/validate",
  "method": "POST",
  "headers": {
    "Authorization": "Bearer ${API_TOKEN}"
  }
}
Request body:
{
  "session_id": "abc123",
  "tool_name": "Bash",
  "tool_input": { ... }
}
Response format:
{
  "decision": "allow",
  "additionalContext": "Validation passed"
}
HTTP hooks enable integration with external validation services, audit systems, and security policies.

Tool Matchers

Control which tools trigger hooks:

Exact Match

{
  "matcher": "Bash",
  "hooks": [ ... ]
}

Multiple Tools

{
  "matcher": "Edit|Write|MultiEdit",
  "hooks": [ ... ]
}

All Tools

{
  "matcher": "*",
  "hooks": [ ... ]
}

Use Cases

Validate commands before execution:
# Check for dangerous patterns
if 'rm -rf' in command:
    print("Dangerous command detected", file=sys.stderr)
    sys.exit(2)  # Block execution
Log all tool usage for compliance:
# Log to central system
log_entry = {
    "session_id": data["session_id"],
    "tool": data["tool_name"],
    "timestamp": datetime.now().isoformat()
}
send_to_audit_system(log_entry)
Transform commands before execution:
# Replace grep with ripgrep
if command.startswith('grep '):
    new_command = command.replace('grep', 'rg', 1)
    print(f"Using rg instead: {new_command}", file=sys.stderr)
Configure environment on session start:
# SessionStart hook
export PROJECT_ENV="development"
source .env.local
echo "Environment configured" >&2
Clean up resources on session end:
# Stop hook
docker-compose down
rm -rf tmp/session-*

Security Considerations

Hooks run with the same permissions as Claude Code. Use caution when:
  • Accepting hooks from untrusted sources
  • Running hooks in production environments
  • Granting hooks network access

Enterprise Controls

Organizations can enforce hook policies:
settings.json
{
  "allowManagedHooksOnly": true,
  "disableAllHooks": false
}
Effects:
  • Users cannot define custom hooks
  • Only managed hooks execute
  • Provides centralized control

Hook Trust

When installing plugins with hooks:
  1. Review hook code before accepting
  2. Check hook permissions and scope
  3. Understand what data hooks can access
  4. Monitor hook execution in logs

Performance

Hook Execution Time

Hooks run synchronously and block tool execution:
  • Keep hooks fast (< 100ms recommended)
  • Use background processes for slow operations
  • Cache results when possible
  • Avoid network calls in hot paths

Optimization Tips

import sys
import json

# Quick validation
data = json.load(sys.stdin)
if is_valid(data):  # Fast check
    sys.exit(0)
sys.exit(2)

Hook Development Workflow

  1. Plan the hook
    • Identify the lifecycle event
    • Determine input/output requirements
    • Choose command or HTTP hook type
  2. Implement the hook
    • Write the hook script
    • Handle JSON input/output
    • Implement error handling
  3. Configure hooks.json
    • Set up matchers
    • Configure hook execution
    • Add description
  4. Test the hook
    • Test with various inputs
    • Verify exit codes
    • Check error messages
  5. Deploy
    • Add to .claude/hooks/
    • Or package in a plugin
    • Document behavior

Debugging Hooks

Enable Debug Logging

import sys

DEBUG_LOG = "/tmp/hook-debug.log"

def log(msg):
    with open(DEBUG_LOG, 'a') as f:
        f.write(f"{datetime.now()}: {msg}\n")

log(f"Hook called with: {json.dumps(data)}")

Check Hook Execution

Hook execution is logged in Claude Code’s debug logs:
# Check if hook executed
grep "hook" ~/.claude/logs/debug.log

# Check for hook errors
grep "hook.*error" ~/.claude/logs/debug.log

Common Issues

  • Verify hooks.json is valid JSON
  • Check matcher pattern matches tool name
  • Ensure hook script has execute permissions
  • Check hook path is correct
  • Check exit code (should be 0 to allow)
  • Verify no stderr output
  • Check for unhandled exceptions
  • Ensure reading from stdin
  • Parse JSON correctly
  • Check for required fields in input

Next Steps

Lifecycle Events

Learn about all available lifecycle events

Hook Examples

See real-world hook implementations

Build docs developers (and LLMs) love