Claude Code Hooks: Automating Your Workflow

"Format automatically after every change." "Stop dangerous commands before they run." Hooks are how you bake these rules into Claude Code: run arbitrary actions at specific lifecycle events. This article walks through where to configure them, the key events, and how blocking works, from a practical angle, based on the official docs.

This article is compiled by Hashito System from the official Claude Code documentation (Anthropic). Hook behavior and event names may be added or changed between versions. Please confirm the latest and exact details in the primary source linked at the end.

1. What a hook is

A hook is a user-defined action that runs automatically at a specific event in the Claude Code lifecycle. It can intervene before or after a tool runs, or when a response is about to stop, to automate shell commands, formatting, and policy enforcement. Unlike asking in a prompt, a hook runs deterministically, every time.

2. Where to configure them

Hooks are configured in JSON. The main locations, in order of precedence, are:

3. Key hook events

There are many events; the most useful ones to know first are:

EventWhen it fires
PreToolUseJust before a tool runs. You can block or rewrite the input here.
PostToolUseRight after a tool runs. Good for formatting or logging.
UserPromptSubmitWhen the user submits a prompt.
StopWhen Claude is about to finish responding.
SubagentStopWhen a subagent stops.
SessionStart / SessionEndAt the start and end of a session.
NotificationWhen Claude Code sends a notification.

4. Writing the config (matcher and command)

The config nests three levels: event name, then matcher, then a hooks array. The matcher narrows which tools apply; use a pipe such as Edit|Write for several, or omit it (or use *) to match all tools. Here is an example that auto-formats after a file is written:

# .claude/settings.json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write \"$CLAUDE_PROJECT_DIR\""
          }
        ]
      }
    ]
  }
}

Hooks receive the event information as JSON on standard input (stdin). Common fields include session_id, cwd, and hook_event_name; tool events also pass tool_name and tool_input. Paths can use placeholders such as ${CLAUDE_PROJECT_DIR} (the project root).

5. Controlling behavior with exit codes

A hook's exit code determines what happens next. This is easy to get wrong, so it matters.

Exit codeBehavior
0Success. Stdout is parsed as structured JSON output. Does not block.
2Blocking error. Stdout JSON is ignored; stderr is fed to Claude as an error message. For PreToolUse and similar, the action is blocked.
OtherNon-blocking error for most events. Execution continues; stderr appears in the transcript.
Note: To enforce a policy (that is, to stop an action), always return exit code 2. exit 1 is treated as non-blocking and will not stop anything. Getting this wrong leads to "I thought I blocked it, but I didn't" incidents.

Here is a minimal PreToolUse example that stops a dangerous delete command. It writes the reason to stderr and exits with code 2.

# .claude/hooks/block-rm.sh (called from PreToolUse)
input=$(cat)   # read JSON from stdin
cmd=$(printf '%s' "$input" | grep -o '"command"[^,}]*')
case "$cmd" in
  *"rm -rf"*)
    echo "rm -rf is not allowed." >&2
    exit 2 ;;   # block with exit code 2
esac
exit 0

6. When to reach for a hook

This aligns with the idea of "harness engineering" that we covered in another article: shaping the environment (the harness) around the agent. Hooks are a strong way to lock that environment down declaratively.

Source

  1. Anthropic, "Hooks" (Claude Code official docs) — https://code.claude.com/docs/en/hooks

Frequently Asked Questions

What are Claude Code hooks?

Hooks are user-defined actions that run automatically at specific events in the Claude Code lifecycle, such as before or after a tool runs or when it stops. You configure them in settings.json to automate command execution, formatting, and policy enforcement.

Where are hooks configured?

Configure them as JSON under the hooks key in ~/.claude/settings.json (user-global), a project's .claude/settings.json (shared), or .claude/settings.local.json (gitignored).

Can a hook block a tool from running?

Yes. If a hook such as PreToolUse exits with code 2, the action is blocked and the stderr text is fed back to Claude as an error message. Note that exit code 1 is treated as non-blocking.

← Back to the blog