Skip to content

Latest commit

 

History

History
244 lines (177 loc) · 5.42 KB

File metadata and controls

244 lines (177 loc) · 5.42 KB

Hooks Reference

Hooks are shell commands or LLM prompts that run automatically in response to Claude Code lifecycle events. They are configured in .claude/settings.json.

Hook Events

SessionStart

When: Session starts, resumes from idle, or context is compressed.

Can block: No

Best for: Injecting project status, displaying a context banner.

{
  "SessionStart": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "echo \"MyProject | Branch: $(git branch --show-current) | Uncommitted: $(git status --porcelain | wc -l) files\""
        }
      ]
    }
  ]
}

Matcher: You can optionally add a "matcher": "compact" to run only on context compaction events rather than every session start.


PreToolUse

When: Before Claude executes a tool (Edit, Write, Bash, etc.)

Can block: Yes — exit code 2 blocks the tool execution.

stdin: Receives JSON with the tool call details:

{
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "/path/to/file.ts",
    "old_string": "...",
    "new_string": "..."
  }
}

Best for: Protecting sensitive files, blocking dangerous operations.

{
  "PreToolUse": [
    {
      "matcher": "Edit|Write",
      "hooks": [
        {
          "type": "command",
          "command": "jq -r '.tool_input.file_path // empty' | grep -q '\\.env$' && echo 'BLOCKED: .env protected' >&2 && exit 2 || true"
        }
      ]
    }
  ]
}

Matcher patterns: Use | to match multiple tools: "Edit|Write", "Bash", "Read|Glob".


PostToolUse

When: After Claude executes a tool successfully.

Can block: No

stdin: Same JSON format as PreToolUse.

Best for: Auto-compilation, auto-linting, logging.

{
  "PostToolUse": [
    {
      "matcher": "Edit|Write",
      "hooks": [
        {
          "type": "command",
          "command": "FILE=$(jq -r '.tool_input.file_path // empty') && case \"$FILE\" in *.ts|*.tsx) ./node_modules/.bin/tsc --noEmit --pretty 2>&1 | tail -20 ;; *.py) mypy . 2>&1 | tail -20 ;; esac; exit 0"
        }
      ]
    }
  ]
}

Stop

When: Claude finishes its response (about to go idle).

Can block: Yes — exit code 2 blocks and forces Claude to continue.

Best for: Uncommitted changes reminders, quality gates.

{
  "Stop": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "CHANGED=$(git diff --name-only 2>/dev/null | wc -l | tr -d ' ') && [ \"$CHANGED\" -gt 0 ] && echo \"Reminder: ${CHANGED} files modified but not committed\" || true"
        }
      ]
    }
  ]
}

TaskCompleted

When: Claude marks a task as completed.

Can block: Yes — exit code 2 blocks task completion.

Best for: Enforcing compilation passes before marking work as done.

{
  "TaskCompleted": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "./node_modules/.bin/tsc --noEmit --pretty 2>&1 | tail -10 && echo 'Type check passed' || (echo 'BLOCKED: Type check failed' >&2 && exit 2)"
        }
      ]
    }
  ]
}

Notification

When: Claude needs user attention (e.g., waiting for input).

Can block: No

Best for: Desktop notifications, Slack alerts.


FileChanged

When: A monitored file changes on disk.

Can block: No

Best for: Hot reload, file synchronization.


Hook Types

Type Description Use Case
command Execute a shell command File protection, compilation, linting
http POST to a URL Slack/Teams notifications, external CI triggers
prompt Single-turn LLM judgment Quick yes/no decisions
agent Multi-step sub-agent verification Complex code review, test validation

Command Hook

The most common type. Runs a shell command and captures stdout/stderr.

{
  "type": "command",
  "command": "echo 'Hello from hook'"
}
  • stdout is shown to Claude as context
  • stderr is shown as warnings/errors
  • exit 0 = success
  • exit 2 = block the operation (PreToolUse, Stop, TaskCompleted only)
  • Other exit codes = treated as errors but don't block

HTTP Hook

Sends a POST request to a URL.

{
  "type": "http",
  "url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
  "headers": { "Content-Type": "application/json" },
  "body": "{\"text\": \"Claude Code session started\"}"
}

Prompt Hook

Runs a single LLM turn for quick judgment.

{
  "type": "prompt",
  "prompt": "Check if this change could break existing functionality. Return JSON: {\"decision\": \"allow\"} or {\"decision\": \"block\", \"reason\": \"...\"}"
}

Agent Hook

Spawns a sub-agent for multi-step verification.

{
  "type": "agent",
  "prompt": "Run the full test suite and report any failures.",
  "model": "sonnet"
}

Tips

  1. Always end command hooks with ; exit 0 if you don't want non-zero exit codes from intermediate commands to accidentally block operations.

  2. Use tail -N to limit output from verbose commands (tsc, mypy) — too much output floods Claude's context.

  3. On Windows, npx may not resolve local binaries correctly. Use ./node_modules/.bin/tsc instead of npx tsc.

  4. Matcher is optional — omit it to run the hook for all tool calls of that event type.

  5. Multiple hooks per event — you can have multiple hook entries for the same event. They run in order.