Skip to content

Settings & Hooks

Hooks provide deterministic automation — unlike prompts (which are suggestions), hooks guarantee execution on specific events.


Why Hooks Over Prompts

Prompts Hooks
Suggestions Claude might follow Guaranteed execution
Can be forgotten in long contexts Always run
Require Claude to remember Event-driven automation
Non-deterministic Fully deterministic

Hook Events

Event When It Fires Best For
PreToolUse Before tool execution Validation, blocking dangerous operations
PostToolUse After successful execution Formatting, linting, tests
UserPromptSubmit When you submit a prompt Adding context, validation
PermissionRequest When Claude asks permission Auto-approve/deny rules
Stop When Claude finishes responding End-of-turn quality gates
SubagentStart When a subagent is spawned Subagent-specific setup
SubagentStop When subagent finishes Subagent-specific cleanup
SessionStart When session begins Environment setup
SessionEnd When session ends Cleanup, notifications

These are the most common events. See the official hooks reference for the full list (21+ events including PostToolUseFailure, Notification, WorktreeCreate, PreCompact, and more).


Exit Codes

Exit Code Meaning
0 Success — continue normally
2 Blocking error — operation is blocked, stderr is fed back to Claude
Any other Non-blocking error — stderr is logged (verbose mode only), execution continues

Essential Hook Configurations

Auto-Lint on Edit (Frontend + Backend)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs -I {} sh -c 'case {} in *.ts|*.tsx|*.js|*.jsx) npx prettier --write {} && npx eslint --fix {} ;; *.py) uv run ruff check --fix {} && uv run ruff format {} ;; esac'"
          }
        ]
      }
    ]
  }
}

Block Dangerous Operations

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.command' | grep -qE '(rm -rf /|DROP TABLE|DELETE FROM.*WHERE 1|> /dev/sd)' && echo 'Blocked dangerous command' >&2 && exit 2 || exit 0"
          }
        ]
      }
    ]
  }
}

Auto-Approve Safe Operations

Use permission rules in settings.json instead of hooks for auto-approving tools:

{
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep",
      "Bash(git status *)",
      "Bash(git diff *)"
    ]
  }
}

End-of-Turn Quality Gate

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "./scripts/quality-gate.sh"
          }
        ]
      }
    ]
  }
}

Example scripts/quality-gate.sh:

#!/bin/bash
set -e

# Frontend checks
if [ -f "package.json" ]; then
    npx tsc --noEmit 2>/dev/null || true
fi

# Backend checks
if [ -f "pyproject.toml" ]; then
    uv run mypy src/ --ignore-missing-imports 2>/dev/null || true
fi

Matcher Patterns

# Match specific tools (regex on tool name)
"matcher": "Edit|Write"

# Match a specific tool
"matcher": "Bash"

# Match MCP tools
"matcher": "mcp__.*"

# Match everything
"matcher": "*"

Full settings.json Template

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "./scripts/post-edit.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "./scripts/quality-gate.sh"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "./scripts/validate-command.sh"
          }
        ]
      }
    ]
  },
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep",
      "Edit",
      "Write",
      "Bash(git *)",
      "Bash(npm *)",
      "Bash(uv *)",
      "Bash(pytest *)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(sudo *)",
      "Bash(curl *)",
      "Bash(wget *)"
    ]
  }
}

Viewing Hooks

/hooks

This opens a read-only browser showing your configured hooks. To add or modify hooks, edit the settings JSON directly or ask Claude to make the change.


Tips

  1. Start with auto-lint — the highest-value hook with minimal friction
  2. Test hooks on one machine before rolling out to the team
  3. Keep hook scripts fast — slow hooks disrupt the development flow
  4. Use exit code 2 sparingly — blocking is disruptive; prefer warnings (exit code 1)
  5. Externalize complex logic to shell scripts rather than inline JSON commands