Claude CodeClaude Code 2.1 · macOS, Linux, Windows
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
31 small wins to finish your pathNext lesson

Hooks: a rule that cannot be argued with

CLAUDE.md is a request. A hook is a program that runs at a fixed moment and can say no, and no model gets a vote.

Hooks fire on events. There are many, and four cover most real uses.

EventWhen it firesGood for
PreToolUseBefore a tool call runs, and it can blockRefusing a command or an edit
PostToolUseAfter a tool call succeedsFormatting a file that was just written
UserPromptSubmitWhen you send a prompt, before Claude sees itAdding context every time
StopWhen Claude finishes a turnGating on tests actually passing

The shape of one

A hook is configured in settings, matched to a tool, and points at a command.

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": ".claude/hooks/block_rm.py" }
        ]
      }
    ]
  }
}

The matcher filters by tool name, so this one only runs for Bash calls. Leaving it out, or using *, runs it on every occurrence of the event.

What the program gets, and what it says back

Claude Code writes JSON to the hook's standard input, describing the tool call:

json
{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" } }

The hook prints JSON back to say what should happen:

json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Destructive command blocked by hook"
  }
}

Exit 0 with no output means the hook has no opinion, and the call carries on through the normal permission flow. That asymmetry is the important part: a hook can deny, but staying quiet is not the same as approving.

The other way to block
Exit code 2 is a blocking error, and it blocks whether or not you printed JSON. The message Claude sees is the reason from your JSON when there is one, and your stderr otherwise.

Hooks and rules together

The two systems do not replace each other, and the docs are precise about where they meet. A hook that allows something does not get past your rules: a matching deny rule still blocks the call, and a matching ask rule still prompts you.

Blocking goes the other way. A hook that exits 2 stops the call before the permission rules are looked at, so the block holds even where an allow rule would have let it through. Refusing is the direction a hook always wins in.

Next lesson writes one and runs it.

Try it yourself
  • Ask Claude how to set up a hook for your project and read what it suggests.
  • Decide which single command in your project should never run without you. That is your first hook.
PreviousSandboxing

Slow is fine. Stopping is the only problem.