← back to posts

Claude Code Hooks: Event-Driven Automation in Your Agent

A hook in Claude Code is a shell command the harness runs in response to a session event. Unlike slash commands (which Claude triggers) or skills (which the model invokes), hooks fire deterministically — they do not depend on the model deciding to call them. That makes them the right tool for anything that must happen: linting, formatting, blocking dangerous commands, sending desktop notifications, logging tool calls.

Hook Events

The harness exposes several events you can hook:

  • SessionStart — Fires when a session starts. Useful for printing a welcome banner, loading project state, or seeding context.
  • UserPromptSubmit — Fires every time you submit a prompt. Can inject extra context or block the prompt entirely.
  • PreToolUse — Fires before any tool call. Can approve, deny, or modify the call. The right place to block rm -rf or enforce a permission policy.
  • PostToolUse — Fires after a tool returns. Use for formatters (run Prettier after every Edit) or audit logs.
  • Stop — Fires when Claude finishes its turn. Common use: a desktop notification so you can switch away while waiting.
  • SubagentStop — Same as Stop but for subagent completion.
  • Notification — Fires when Claude sends a notification (waiting for input, permission request).

Defining Hooks

Hooks live in .claude/settings.json (project) or ~/.claude/settings.json (user). The schema:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write \"$CLAUDE_FILE_PATHS\""
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "powershell -c \"[console]::beep(800,200)\""
          }
        ]
      }
    ]
  }
}

The matcher field is a regex against the tool name. Omit it to match every event of that type. Hook commands receive event metadata via environment variables (CLAUDE_FILE_PATHS, CLAUDE_TOOL_NAME, CLAUDE_PROJECT_DIR, etc.) and through stdin as JSON.

Real Patterns That Pay Off

Auto-format on every edit. The Prettier example above is the highest-ROI hook. You stop caring whether Claude got the indentation right because it gets fixed automatically.

Block dangerous commands. A PreToolUse hook that scans for rm -rf /, git push --force to main, or DROP TABLE and exits non-zero will refuse the call. The harness honors the exit code.

1
2
3
4
5
6
7
8
9
{
  "PreToolUse": [{
    "matcher": "Bash",
    "hooks": [{
      "type": "command",
      "command": "node ./scripts/guard-bash.js"
    }]
  }]
}

Desktop notification on stop. On Windows, a one-liner using BurntToast or PowerShell’s beep keeps long agent runs ergonomic — start the task, switch tabs, get pulled back when done.

Inject project state. A SessionStart hook that runs git status --short and prints the result seeds Claude with the current working tree without waiting for it to ask.

Audit log. A PostToolUse hook that appends {tool, args, timestamp} to a JSONL file gives you a forensic record of what the agent did, which is invaluable when something goes sideways.

Exit Codes Matter

For PreToolUse and UserPromptSubmit, the hook’s exit code controls whether the action proceeds:

  • 0 — Allow.
  • 2 — Block, and feed the hook’s stderr back to Claude as a reason. Claude will see the message and adjust.
  • Anything else — Treated as an error, but the action proceeds.

This is what makes hooks a real policy layer rather than just notifications.

A Note on Trust

Hooks run with your user permissions. They execute every event, every session, often without confirmation. Treat .claude/settings.json like any other code — review it before pulling someone else’s project, and never paste hook configs from untrusted sources.

Used well, hooks turn Claude Code from a smart REPL into a disciplined collaborator that respects the rules of your codebase whether the model remembers to or not.