Hooks Reference

January 31, 2026 ยท View on GitHub

How Claude Code Hooks Work

Claude Code supports hooks that execute shell commands at specific lifecycle events. Hooks are configured in ~/.claude/settings.json.

Hook Events

EventWhenStdinCan Block
SessionStartClaude Code session starts(none)No
PreToolUseBefore a tool executesTool invocation JSONYes (exit 2)
PostToolUseAfter a tool executesTool result JSONNo

Exit Codes

CodeMeaningEffect
0Success/AllowProceed normally
1ErrorProceed, but log the error
2BlockReject the tool execution (PreToolUse only)

Stdin Format

PreToolUse:

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "ls -la"
  }
}

PostToolUse:

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "ls -la"
  },
  "tool_output": "total 42\n..."
}

Stdout (PreToolUse only)

When exiting with code 2, any stdout is shown to the user as the rejection reason:

BLOCKED: Dangerous command pattern detected: 'rm -rf /'

Settings Format

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": ".*",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.claude/hooks/pre_tool_use.py"
          }
        ]
      }
    ]
  }
}

The matcher field is a regex matched against the tool name. Use ".*" to match all tools, or specific patterns like "Bash|Write|Edit".

Provided Hook Scripts

session_start.py

  • Runs file integrity verification
  • Reports verified/unsigned/tampered/missing counts
  • Exits 2 if tampered files detected

pre_tool_use.py

  • Blocks dangerous Bash commands (rm -rf /, mkfs, fork bombs)
  • Runs security gate on Write/Edit/Bash content
  • Checks approval tokens for monitored file modifications

post_tool_use.py

  • Captures self-modification diffs for Write/Edit on monitored paths
  • Appends to hash-chained tamper-proof audit log

security_orchestrator.py

  • Combines all pre-tool checks into a single hook
  • Runs: dangerous commands, injection, file integrity
  • Use instead of pre_tool_use.py for the full check suite

Custom Hooks

Create your own hook at ~/.claude/hooks/my_hook.py:

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

data = json.loads(sys.stdin.read())
tool_name = data.get("tool_name", "")

if tool_name == "Bash" and "sudo" in data.get("tool_input", {}).get("command", ""):
    print("BLOCKED: sudo commands not allowed")
    sys.exit(2)

sys.exit(0)

Add to settings:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{"type": "command", "command": "python3 ~/.claude/hooks/my_hook.py"}]
      }
    ]
  }
}