Skip to content

Hooks — Automated Guardrails for Claude Code

Hooks are shell commands that run automatically at key points in Claude Code's lifecycle. They can block dangerous operations, enforce standards, validate code, or log activity.

How Hooks Work

User Request → Claude picks tool → [PreToolUse Hook] → Tool executes → [PostToolUse Hook]
                                   Exit code 0 = allow
                                   Exit code 2 = block (with message)

All Hook Events (13 events)

Lifecycle Hooks

Event When Use For
Setup Repository initialization or periodic maintenance Inject environment, set up dependencies
SessionStart Session begins (startup, resume, clear) Load context, log session start
SessionEnd Session ends Cleanup temp files, log session duration

Conversation Hooks

Event When Use For
UserPromptSubmit User submits a prompt Validate input, log prompts, store session data

Tool Execution Hooks

Event When Use For
PreToolUse Before tool executes Block dangerous edits, validate input, security checks
PostToolUse After tool succeeds Auto-format, lint validation, verify output
PostToolUseFailure After tool fails Log failures, error analysis

Permission Hooks

Event When Use For
PermissionRequest Claude requests permission Auto-allow safe operations, log permission decisions

Notification Hooks

Event When Use For
Notification Claude sends a notification Alerts, TTS announcements, logging

Completion Hooks

Event When Use For
Stop Claude finishes a response Generate summaries, cleanup, notify user
SubagentStart A sub-agent spawns Log sub-agent activity, debug
SubagentStop A sub-agent finishes Track sub-agent completion

Maintenance Hooks

Event When Use For
PreCompact Before context compaction Backup transcripts, save state

Configuration

Add hooks to .claude/settings.local.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/security-check.py"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/lint-on-edit.py"
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/stop-summary.py"
          }
        ]
      }
    ]
  }
}

Matcher Patterns

  • Write — matches Write tool only
  • Write|Edit — matches Write or Edit
  • Bash — matches Bash tool
  • * — matches all tools (use sparingly)
  • "" (empty) — for events without tool matchers (Stop, SessionStart, etc.)

Hook Input

Hooks receive JSON on stdin with context about the event:

PreToolUse / PostToolUse:

{
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.py",
    "content": "file contents..."
  }
}

Stop:

{
  "stop_hook_active": true
}

Hook Output

  • Exit 0: Allow the operation (stdout ignored for most hooks)
  • Exit 2: Block the operation (stdout shown as reason)
  • Any other exit: Treated as allow (fail-open for safety)

Advanced — JSON output (for hooks that need granular control):

{
  "decision": "block",
  "reason": "Lint errors found: 3 violations"
}

Available Hook Scripts

PreToolUse Hooks

security-check.py

Blocks edits that introduce secrets, API keys, or credentials.

Detects: API keys, secret keys, bearer tokens, GitHub tokens, private keys, hardcoded passwords.

Skips: .env.example, test files, .claude/ directory.

cp hooks/security-check.py /your-project/.claude/hooks/

protect-files.py

Blocks edits to protected files (lock files, generated files, .env).

cp hooks/protect-files.py /your-project/.claude/hooks/

permission-auto-allow.py

Auto-allows safe read-only operations without prompting.

cp hooks/permission-auto-allow.py /your-project/.claude/hooks/

PostToolUse Hooks

format-on-edit.py

Auto-formats files after editing (runs formatters by file extension).

cp hooks/format-on-edit.py /your-project/.claude/hooks/

lint-on-edit.py

Runs linter after file edits and blocks if errors found (validation gate).

cp hooks/lint-on-edit.py /your-project/.claude/hooks/

Completion Hooks

stop-summary.py

Logs a summary when Claude finishes a task.

cp hooks/stop-summary.py /your-project/.claude/hooks/

Audit Hooks

log-commands.sh

Logs all Bash commands to an audit file for review.

cp hooks/log-commands.sh /your-project/.claude/hooks/
chmod +x /your-project/.claude/hooks/log-commands.sh

Installation

  1. Copy desired hook scripts to .claude/hooks/ in your project
  2. Add hook configuration to .claude/settings.local.json
  3. Test by triggering the hook (e.g., try to write a file with a fake API key)

Tips

  • Hooks should be fast (<1 second) — they run on every matching tool call
  • Always fail-open (exit 0 on errors) to avoid blocking legitimate work
  • Use matcher to limit which tools trigger the hook
  • Test hooks before relying on them
  • For PostToolUse validation (lint, type check), use exit 2 to block and show errors
  • Keep audit logs in .claude/audit/ (gitignored)