How-To Field Guide

How to Use Claude Code Hooks

Stop retyping the same instruction every session. Hooks turn 'please run the formatter' into a rule the tool can't forget — four steps, straight from the docs.

A developer at a dark desk, an editor full of code open across a monitor and a laptop.
Photo by Tai Bui on Unsplash
The receipts
  • A hook is a shell command Claude Code runs itself at a fixed point in its lifecycle — deterministic, not a suggestion the model can skip.
  • Config is a `hooks` block in a settings file. Which file you pick decides the blast radius: your machine, the whole repo, or just you on this project.
  • PostToolUse runs after a tool succeeds. PreToolUse runs before it and can block it — exit code 2 is the kill switch, and your stderr becomes the reason Claude sees.
  • Type `/hooks` to confirm it registered. If it isn't in that list, your JSON is wrong — stop debugging the behavior.
Short answer

Claude Code hooks are user-defined shell commands that Claude Code runs automatically at specific points in its lifecycle. You configure them in a `hooks` block in a settings file, scoped by event name and a matcher. PostToolUse hooks fire after a tool succeeds; PreToolUse hooks fire before a tool call and can block it.

Every session, same sentence out of me: “after you edit a file, run the formatter.” Sometimes it did. Sometimes it got four tasks deep, filled its head with the actual problem, and dropped it.

That’s not a model failure. That’s me asking an LLM to be a cron job.

Hooks are the fix. A hook is a shell command Claude Code runs itself at a fixed point in its own lifecycle, and Anthropic’s framing in the hooks guide is the whole pitch: deterministic control, instead of relying on the model to choose to run it.

I got my start in Ops. Deterministic beats persuasive… every single time.

Step 1: Decide the blast radius

Hooks live in a hooks block in a settings file. Which file you pick is the decision:

  • ~/.claude/settings.json — you, every project on the machine.
  • .claude/settings.json — everyone who clones the repo. Commit it.
  • .claude/settings.local.json — you, this project, gitignored.

Local beats shared, shared beats user — the full precedence chain is in the settings docs. Team standards go in the shared file. Your personal desktop notification does not.

Step 2: Format on every edit

PostToolUse fires after a tool call succeeds. Scope it with a matcher so it only runs on the editing tools:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

Claude Code pipes the event JSON to your command on stdin. jq pulls the edited path out of it. Swap Prettier for ruff, gofmt, whatever your repo actually runs.

Step 3: Put a fence around the files it must never touch

PreToolUse fires before a tool call and can block it. Point it at a script that reads stdin, checks the target path against a protected list — .env, lockfiles, anything under .git/ — prints a reason to stderr, and exits 2.

Exit code 2 is the whole trick. On PreToolUse it’s a blocking error: the call never executes, and your stderr text goes back to Claude as feedback so it can adjust instead of retrying blind. Exit 0 means proceed. Anything else is a non-blocking error — you see a notice, the action still happens.

Read the per-event table in the hooks reference before you get clever. The exceptions are real: PostToolUse can’t block anything, because the tool already ran.

Step 4: Verify it registered

Type /hooks. You get a read-only browser of every event, a count of what’s configured, and for each hook the matcher, type, source file, and command.

If your hook isn’t in that list, the JSON is wrong. Don’t debug the behavior. Debug the file.

Where they bite

Hooks run shell commands on your machine, automatically, with your credentials. A hooks block in a repo you cloned is executable code you didn’t write. Read it like a postinstall script.

And keep matchers narrow. A wildcard on a hook that makes permission decisions is a hook that auto-approves everything, which is the opposite of a guardrail.

Last one, and this is the line I keep having to redraw: hooks are for rules, not judgment. “Format every edited file” is a rule. “Is this refactor any good” is not — that’s subagent territory, or a prompt- or agent-type hook that spends a model call on purpose. Claude Code also ships an mcp_tool hook type, so a rule can call straight into your MCP servers.

Twenty minutes of setup buys back an instruction you’d otherwise retype forever. Same energy as Claude Code artifacts — small surface, disproportionate payoff.

Stop asking the agent to remember. Make the tool enforce it.

#TheAIMogul

Bottom lineHooks are the best twenty minutes you'll spend on an AI coding setup. Use them for rules, never for judgment.

Frequently asked

What is a hook in Claude Code?
A hook is a user-defined shell command that Claude Code runs automatically at a specific point in its lifecycle — before a tool call, after an edit, when a session starts, when it finishes responding. The point is deterministic control: the command always runs, rather than depending on the model deciding to run it.
Where do I put Claude Code hooks?
In a `hooks` block inside a settings file. `~/.claude/settings.json` applies to every project on your machine, `.claude/settings.json` in a project root is shared with everyone who clones the repo, and `.claude/settings.local.json` is yours alone and gitignored. Claude Code's settings documentation gives the full precedence order.
How do I stop Claude Code from editing a specific file?
Register a PreToolUse hook with a matcher for the editing tools, pointing at a script that reads the tool input from stdin, checks the target path against your protected list, and exits with code 2. On PreToolUse, exit code 2 is a blocking error: the tool call never runs, and whatever the script printed to stderr is passed back to Claude as the reason.
How do I know my hook is actually running?
Type `/hooks` in Claude Code. It opens a read-only browser showing every hook event, a count of configured hooks, and for each one the matcher, type, source file, and command. The menu will not let you edit hooks — you change them by editing the settings JSON directly.
Do hooks only run shell commands?
No. `command` is the common type, but Claude Code also documents an `http` type that POSTs event data to a URL, an `mcp_tool` type that calls a tool on a connected MCP server, and prompt- and agent-based types that use a Claude model to evaluate a condition when the rule needs judgment instead of a string match.