Claude Code Hooks - Complete Event-Driven Automation Guide

Hooks are automated scripts that execute in response to specific events during Claude Code sessions. They enable automation, validation, permission management, and custom workflows.

What Are Hooks?

Hooks are automated actions that execute automatically when specific events occur in Claude Code. They receive JSON input and communicate results via exit codes and JSON output.

Key features:

  • Event-driven automation
  • JSON-based input/output
  • Support for command, HTTP, prompt, and agent hook types
  • Pattern matching for tool-specific hooks

Hooks Flow Overview

Hooks Flow

Hook events trigger pattern matching, which routes to different hook types (Command, HTTP, Prompt, Agent). Each hook can return allow, block, or modify decisions.

Hook Types

Claude Code supports four hook types:

1. Command Hooks (Default)

Executes a shell command and communicates via JSON stdin/stdout and exit codes.

{
  "type": "command",
  "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate.py\"",
  "timeout": 60
}

2. HTTP Hooks

Remote webhook endpoints that receive JSON input and return JSON response.

{
  "hooks": {
    "PostToolUse": [{
      "type": "http",
      "url": "https://my-webhook.example.com/hook",
      "matcher": "Write"
    }]
  }
}

3. Prompt Hooks

LLM-evaluated prompts for intelligent task completion checking.

{
  "type": "prompt",
  "prompt": "Evaluate if Claude completed all requested tasks.",
  "timeout": 30
}

4. Agent Hooks

Subagent-based verification hooks that can use tools and perform multi-step reasoning.

{
  "type": "agent",
  "prompt": "Verify the code changes follow our architecture guidelines.",
  "timeout": 120
}

All Hook Events (25 Events)

EventWhen TriggeredCan BlockCommon Use
SessionStartSession begins/resumesNoEnvironment setup
InstructionsLoadedAfter CLAUDE.md loadedNoModify instructions
UserPromptSubmitUser submits promptYesValidate prompts
PreToolUseBefore tool executionYesValidate, modify inputs
PermissionRequestPermission dialog shownYesAuto-approve/deny
PostToolUseAfter tool succeedsNoAdd context, feedback
PostToolUseFailureTool execution failsNoError handling
NotificationNotification sentNoCustom notifications
SubagentStartSubagent spawnedNoSubagent setup
SubagentStopSubagent finishesYesSubagent validation
StopClaude finishesYesTask completion check
StopFailureAPI error ends turnNoError recovery
TeammateIdleAgent team idleYesTeammate coordination
TaskCompletedTask marked completeYesPost-task actions
TaskCreatedTask createdNoTask tracking
ConfigChangeConfig file changesYesReact to updates
CwdChangedWorking directory changesNoDirectory setup
FileChangedWatched file changesNoFile monitoring
PreCompactBefore context compactionNoPre-compact actions
PostCompactAfter compactionNoPost-compact actions
WorktreeCreateWorktree being createdYesWorktree init
WorktreeRemoveWorktree removedNoWorktree cleanup
ElicitationMCP server requests inputYesInput validation
ElicitationResultUser responds to elicitationYesResponse processing
SessionEndSession terminatesNoCleanup, logging

Configuration

File Locations

LocationScopeShared
~/.claude/settings.jsonUser settingsAll projects
.claude/settings.jsonProject settingsVia git
.claude/settings.local.jsonLocal settingsNot committed
Plugin hooks/hooks.jsonPlugin-scopedVia plugin
Skill/Agent frontmatterComponent lifetimeVia skill

Basic Structure

{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

Configuration Fields

FieldDescriptionExample
matcherPattern to match tool names"Write", "Edit\|Write", "*"
hooksArray of hook definitions[{ "type": "command", ... }]
typeHook type"command", "prompt", "http", "agent"
commandShell command to execute"$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh"
timeoutTimeout in seconds (default 60)30
onceRun only once per sessiontrue

Matcher Patterns

PatternDescriptionExample
Exact stringMatches specific tool"Write"
Regex patternMatches multiple tools"Edit\|Write"
WildcardMatches all tools"*" or ""
MCP toolsServer and tool pattern"mcp__memory__.*"

PreToolUse Hooks

Runs before tool execution. Can block or modify the action.

Example: Block Dangerous Commands

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py"
          }
        ]
      }
    ]
  }
}

Script: validate-bash.py

#!/usr/bin/env python3
import json
import sys

data = json.load(sys.stdin)
command = data.get("input", {}).get("command", "")

# Block dangerous patterns
dangerous = ["rm -rf", "DROP TABLE", "DELETE FROM", ":(){ :|:& };:"]
for pattern in dangerous:
    if pattern in command:
        print(json.dumps({
            "decision": "deny",
            "reason": f"Blocked dangerous pattern: {pattern}"
        }))
        sys.exit(0)

# Allow
sys.exit(0)

Example: Validate File Writes

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-write.py"
          }
        ]
      }
    ]
  }
}

Output control:

  • permissionDecision: "allow", "deny", or "ask"
  • permissionDecisionReason: Explanation for decision
  • updatedInput: Modified tool input parameters

PostToolUse Hooks

Runs after tool completion. Useful for verification and logging.

Example: Security Scan After Write

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py"
          }
        ]
      }
    ]
  }
}

Script: security-scan.py

#!/usr/bin/env python3
import json
import sys
import re

data = json.load(sys.stdin)
content = data.get("result", {}).get("content", "")

# Check for secrets
patterns = [
    (r'api[_-]?key\s*=\s*["\'][^"\']+["\']', "API key detected"),
    (r'password\s*=\s*["\'][^"\']+["\']', "Password detected"),
    (r'secret\s*=\s*["\'][^"\']+["\']', "Secret detected"),
]

issues = []
for pattern, message in patterns:
    if re.search(pattern, content, re.IGNORECASE):
        issues.append(message)

if issues:
    print(json.dumps({
        "additionalContext": f"⚠️ Security Warning: {', '.join(issues)}"
    }))

sys.exit(0)

Example: Auto-Format Code

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "prettier --write \"$FILE_PATH\""
          }
        ]
      }
    ]
  }
}

Stop and SubagentStop Hooks

Run when Claude finishes or a subagent completes. Supports prompt-based evaluation.

Example: Task Completion Check

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Evaluate if Claude completed all requested tasks. If not, explain what's missing.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

UserPromptSubmit Hooks

Runs when user submits a prompt, before Claude processes it.

Example: Validate User Prompts

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py"
          }
        ]
      }
    ]
  }
}

SessionStart Hooks

Runs when session begins, resumes, clears, or compacts.

Example: Environment Setup

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-init.sh"
          }
        ]
      }
    ]
  }
}

Script: session-init.sh

#!/bin/bash

# Log session start
echo "$(date): Session started" >> ~/.claude/session.log

# Check for required tools
if ! command -v node &> /dev/null; then
    echo '{"additionalContext": "⚠️ Node.js is not installed"}'
fi

exit 0

Hook Input/Output

Input (stdin)

Hooks receive JSON input via stdin:

{
  "tool": "Bash",
  "input": {
    "command": "npm test"
  },
  "result": {
    "output": "All tests passed",
    "exitCode": 0
  }
}

Output (stdout)

Exit CodeMeaning
0Allow/success
2Block with error
OtherUnexpected error

Output Fields

{
  "decision": "block",
  "reason": "Explanation for blocking",
  "additionalContext": "Context to add for Claude",
  "permissionDecision": "allow|deny|ask",
  "updatedInput": {
    "modified": "input parameters"
  }
}

Skill-Scoped Hooks

Define hooks within a skill:

---
name: safe-deploy
description: Deploy with safety checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/pre-deploy-check.sh"
  PostToolUse:
    - matcher: "Bash(git push*)"
      hooks:
        - type: command
          command: "./scripts/notify-team.sh"
---

Deploy the application with safety checks...

Practical Examples

Example 1: Comprehensive Security Hook

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/security-check.py"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/secret-scan.py"
          }
        ]
      }
    ]
  }
}

Example 2: Git Workflow Automation

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(git push*)",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/pre-push.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash(git push*)",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/post-push.sh"
          }
        ]
      }
    ]
  }
}

Example 3: Notification System

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash(npm run deploy*)",
        "hooks": [
          {
            "type": "http",
            "url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
          }
        ]
      }
    ]
  }
}

Example 4: Auto-Testing

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npm test -- --related --passWithNoTests"
          }
        ]
      }
    ]
  }
}

Best Practices

  1. Keep hooks fast: They run synchronously (use timeout)
  2. Handle errors gracefully: Don’t break the workflow
  3. Log everything: For debugging and auditing
  4. Test thoroughly: Verify hooks work as expected
  5. Document clearly: Explain what each hook does
  6. Use environment variables: $CLAUDE_PROJECT_DIR for project paths
  7. Return proper exit codes: 0 for success, 2 for block

Debugging Hooks

Enable Debug Logging

CLAUDE_DEBUG_HOOKS=1 claude

Test Hook Script

# Test manually with JSON input
echo '{"tool":"Bash","input":{"command":"test"}}' | python3 ./hooks/my-hook.py

# Check exit code
echo $?

Common Issues

IssueSolution
Hook not runningCheck matcher pattern
Permission deniedMake script executable (chmod +x)
TimeoutIncrease timeout field
Wrong exit codeReturn proper JSON output