← All guides

Claude Code Hooks: Automate and Control Every Tool Call

How to configure Claude Code hooks — shell commands that fire before or after tool use — with real examples for logging, enforcement, and automation.

Claude Code Hooks: Automate and Control Every Tool Call

Claude Code hooks are shell commands that execute automatically before or after tool calls. They give you programmatic control over what Claude Code does — logging every file it touches, blocking certain edits, running formatters after writes, or sending Slack notifications on commit.

This guide covers every hook type, how to configure them, and 12 production examples.

What hooks are

Every time Claude Code calls a tool (Read, Edit, Write, Bash, etc.), two hook points fire:

Hooks are configured in your Claude Code settings (~/.claude/settings.json for user-level, or .claude/settings.json for project-level). Project-level settings override user-level for the hooks they define.

Hook configuration format

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"Running bash: $CLAUDE_TOOL_INPUT\" >> /tmp/claude-audit.log"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "/usr/local/bin/prettier --write \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}

Key fields:

Environment variables available to hooks

Variable Content Available in
CLAUDE_TOOL_NAME Name of the tool being called Pre and Post
CLAUDE_TOOL_INPUT JSON-encoded input to the tool Pre only
CLAUDE_TOOL_RESULT JSON-encoded result from the tool Post only
CLAUDE_FILE_PATH File path for file-operation tools Pre and Post
CLAUDE_SESSION_ID Current Claude Code session ID Pre and Post
CLAUDE_WORKING_DIR Current working directory Pre and Post

Blocking a tool call with PreToolUse

If a PreToolUse hook exits with a non-zero status, Claude Code blocks the tool call and shows the hook's stderr output to Claude and to you. Claude can then decide how to proceed without the blocked action.

This is the safety enforcement mechanism.

12 production hook examples

1. Audit log — every tool call

{
  "PreToolUse": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "command",
          "command": "echo \"$(date -u +%Y-%m-%dT%H:%M:%SZ) $CLAUDE_TOOL_NAME $CLAUDE_FILE_PATH\" >> ~/claude-audit.log"
        }
      ]
    }
  ]
}

Writes a timestamped line to ~/claude-audit.log for every tool call. Use for compliance or debugging.

2. Block edits to protected files

{
  "PreToolUse": [
    {
      "matcher": "Edit",
      "hooks": [
        {
          "type": "command",
          "command": "if echo \"$CLAUDE_FILE_PATH\" | grep -qE '(\.env|secrets|credentials|private_key)'; then echo 'BLOCKED: editing secrets files is not allowed' >&2; exit 1; fi"
        }
      ]
    }
  ]
}

Blocks any Edit to files matching the pattern (.env, secrets, credentials, private_key). Claude sees the error message and cannot edit those files.

3. Block edits to production config

{
  "PreToolUse": [
    {
      "matcher": "Edit",
      "hooks": [
        {
          "type": "command",
          "command": "if echo \"$CLAUDE_FILE_PATH\" | grep -qE 'prod|production'; then echo 'BLOCKED: direct edits to production config require human review' >&2; exit 1; fi"
        }
      ]
    }
  ]
}

Prevents Claude from editing any file with "prod" or "production" in the path without human confirmation.

4. Auto-format after Write

{
  "PostToolUse": [
    {
      "matcher": "Write",
      "hooks": [
        {
          "type": "command",
          "command": "if echo \"$CLAUDE_FILE_PATH\" | grep -qE '\\.(ts|tsx|js|jsx)$'; then npx prettier --write \"$CLAUDE_FILE_PATH\" 2>/dev/null; fi"
        }
      ]
    }
  ]
}

Runs Prettier on TypeScript/JavaScript files after every Write. Ensures consistent formatting without requiring Claude to know your Prettier config.

5. Auto-format Python files

{
  "PostToolUse": [
    {
      "matcher": "Write",
      "hooks": [
        {
          "type": "command",
          "command": "if echo \"$CLAUDE_FILE_PATH\" | grep -qE '\\.py$'; then black \"$CLAUDE_FILE_PATH\" 2>/dev/null; fi"
        }
      ]
    }
  ]
}

Runs black after every .py file write.

6. Run type checker after TypeScript edits

{
  "PostToolUse": [
    {
      "matcher": "Edit",
      "hooks": [
        {
          "type": "command",
          "command": "if echo \"$CLAUDE_FILE_PATH\" | grep -qE '\\.tsx?$'; then cd \"$CLAUDE_WORKING_DIR\" && npx tsc --noEmit 2>&1 | tail -5 >> /tmp/tsc-errors.log; fi"
        }
      ]
    }
  ]
}

Appends TypeScript type errors to a log file after every .ts/.tsx edit. Review the log after a session to catch type regressions.

7. Block dangerous Bash patterns

{
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hooks": [
        {
          "type": "command",
          "command": "BLOCKED_PATTERNS='rm -rf|DROP TABLE|DELETE FROM.*WHERE 1|kill -9'; if echo \"$CLAUDE_TOOL_INPUT\" | grep -qiE \"$BLOCKED_PATTERNS\"; then echo \"BLOCKED: command matches a dangerous pattern. Request human confirmation.\" >&2; exit 1; fi"
        }
      ]
    }
  ]
}

Blocks bash commands matching destructive patterns. Extend BLOCKED_PATTERNS for your threat model.

8. Notify on file write (macOS)

{
  "PostToolUse": [
    {
      "matcher": "Write",
      "hooks": [
        {
          "type": "command",
          "command": "osascript -e 'display notification \"'\"$CLAUDE_FILE_PATH\"'\" with title \"Claude wrote a file\"' 2>/dev/null || true"
        }
      ]
    }
  ]
}

Sends a macOS notification every time Claude writes a file. Useful when running long autonomous tasks in the background.

9. Log cost-relevant token usage

{
  "PostToolUse": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "command",
          "command": "echo \"$(date -u +%Y-%m-%dT%H:%M:%SZ),${CLAUDE_SESSION_ID},${CLAUDE_TOOL_NAME}\" >> ~/claude-usage.csv"
        }
      ]
    }
  ]
}

Logs every tool call with timestamp and session ID. Useful for auditing which sessions are most tool-heavy.

10. Prevent git push without test passage

{
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hooks": [
        {
          "type": "command",
          "command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -q 'git push'; then cd \"$CLAUDE_WORKING_DIR\" && npm test --silent 2>&1; if [ $? -ne 0 ]; then echo 'BLOCKED: tests must pass before push' >&2; exit 1; fi; fi"
        }
      ]
    }
  ]
}

Intercepts git push commands and runs the test suite. Blocks the push if tests fail.

11. Enforce branch naming convention

{
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hooks": [
        {
          "type": "command",
          "command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'git checkout -b|git switch -c'; then BRANCH=$(echo \"$CLAUDE_TOOL_INPUT\" | grep -oE '[a-zA-Z0-9/_-]+$'); if ! echo \"$BRANCH\" | grep -qE '^(feat|fix|chore|docs|test)/'; then echo \"BLOCKED: branch names must start with feat/, fix/, chore/, docs/, or test/\" >&2; exit 1; fi; fi"
        }
      ]
    }
  ]
}

Blocks branch creation if the branch name does not follow your convention. Claude will use the correct prefix on the next attempt.

12. Session start hook (UserPromptSubmit)

{
  "UserPromptSubmit": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "command",
          "command": "echo \"$(date -u +%Y-%m-%dT%H:%M:%SZ) SESSION_START $CLAUDE_SESSION_ID\" >> ~/claude-sessions.log"
        }
      ]
    }
  ]
}

UserPromptSubmit fires when you submit a message. Use it to log session starts, enforce context (e.g., check git status before every session), or inject environment-specific instructions.

Hook types summary

Hook event When it fires Can block?
PreToolUse Before tool execution Yes (exit non-zero)
PostToolUse After tool execution No
UserPromptSubmit When user sends a message Yes (exit non-zero)
Notification When Claude sends a notification No

Project vs user hooks

User-level (~/.claude/settings.json): apply to all Claude Code sessions regardless of directory. Good for personal audit logging and safety rails.

Project-level (.claude/settings.json in your project root): apply only when Claude Code is run from within that project. Good for project-specific formatters, test runners, and branch conventions.

When both exist, project hooks are appended to user hooks — they both run, not one or the other.

Debugging hooks

If a hook is not firing or behaving unexpectedly:

  1. Run the hook command manually in your shell with the expected env vars set.
  2. Check for exit code issues — a hook that exits 0 is "success"; anything else blocks the call.
  3. Capture hook stderr: redirect to a file with 2>> /tmp/hook-debug.log.
  4. Use claude --debug to see hook execution in the Claude Code debug output.

FAQ

Can hooks access the full content of a file being written? Not directly via environment variables — only the file path is provided. For Write hooks, the file has already been written when PostToolUse fires, so you can read it at $CLAUDE_FILE_PATH.

Can a hook call Claude? Yes — a hook command can call claude -p "..." non-interactively. This enables meta-patterns like having a hook analyze every edit for security issues. Use sparingly; it adds latency and API cost per tool call.

Can hooks modify the tool input before execution? No. PreToolUse hooks can block calls but cannot modify the input. The tool either runs as Claude requested or is blocked.

Are hooks shared with teammates via version control? Project-level .claude/settings.json can be committed. This is the recommended approach for team-shared hooks (formatters, safety rails). User-level ~/.claude/settings.json is personal and not shared.

What is the timeout for hook execution? Hooks have a 60-second timeout by default. Long-running hooks (e.g., running the full test suite before every Bash call) can cause Claude Code to appear to hang. Use timeouts in your hook commands where appropriate.

Sources

  1. Claude Code hooks documentation — Anthropic, April 2026
  2. Claude Code settings reference — April 2026
AI Disclosure: Drafted with Claude Code; examples verified against Claude Code documentation and tested in production April 2026.