Builders Camp

Tools

Claude Code hooks: what they enforce that CLAUDE.md cannot, and how to use them in a product workflow

Claude Code hooks are commands Claude Code runs at a fixed lifecycle event, and a PreToolUse hook that exits with code 2 blocks the tool call no matter what the model decided, which is the one thing a rule in CLAUDE.md cannot do. Use CLAUDE.md for rules that can occasionally slip, and move a rule into a hook once it has slipped twice or once a single slip would be expensive.

What do Claude Code hooks enforce that CLAUDE.md cannot?

A hook runs whether or not the model agrees with it, and that is the whole difference. Anthropic's memory documentation says Claude treats CLAUDE.md files and auto memory "as context, not enforced configuration", and points to a PreToolUse hook for any action that must be blocked regardless of what Claude decides. Anthropic's Extend Claude Code page puts the rule in one line: "If a rule must hold every time, make it a hook rather than a prompt instruction."

The mechanics are narrow and worth knowing exactly. The hooks reference lists 33 lifecycle events as of 27 September 2026, from SessionStart to SessionEnd, and each event documents what a hook can do there. Only some events can stop something. A PreToolUse hook runs before a tool call and blocks it when it exits with code 2. A PostToolUse hook runs after the tool already ran, so exit code 2 there only shows your message to Claude. The count will drift as Anthropic adds events, so read the reference rather than a blog post, including this one, when you need the current list.

CLAUDE.md rule Hook
What it is Text loaded into context every session A command Claude Code runs at a lifecycle event
Who decides whether it applies The model, every time The matcher, every time
Can it stop an action No, it can only make the action less likely Yes, on events that can block, such as PreToolUse
Context cost Every line loads every session None unless the hook returns output
Good for Conventions, tone, "prefer X" "Never touch this path", logging, injecting fresh context

When should a CLAUDE.md rule become a hook?

Move it the second time it is ignored, or the first time a single violation would cost more than an afternoon. A rule in CLAUDE.md that slips once is a nudge you can tighten: make it shorter and more specific, because the memory documentation says specific, concise instructions are followed more consistently. A rule that slips again after tightening is telling you the model is weighing it against something else in context, and more wording will not change that. That rule belongs in a hook.

A second trigger skips the waiting. If a slip is expensive or irreversible (an edit to the credentials file, a write to a signed-off spec, a command against production), do not wait for the second slip. Anthropic's own example is a .env file: an instruction never to edit it is a request, and a PreToolUse hook that blocks the edit is enforcement.

There is a level above the hook, too. A hook guards the agent's own session on one machine. It does nothing about a change that arrives by another route, such as an engineer editing the file by hand or a second tool with its own configuration. When a rule has to hold for everyone who touches the repository, it belongs in a CI check that runs on every pull request, where no session setting can skip it. For where skills and subagents fit alongside CLAUDE.md and hooks, see CLAUDE.md vs skills vs hooks vs subagents.

Which product problem is actually a hook?

The one where the fix is always the same three sentences and you are always the one typing them. If you have told an agent twice this week which quarter it is, or reminded it twice that the pricing page copy needs legal sign-off, you have found a hook. The glossary entry on automation hooks covers the vocabulary. The sections below build one hook that adds context, one that blocks, and one that logs.

The test is mechanical. Something that should happen at a predictable moment, every time, without judgement, is a hook. Something that needs judgement is not, and trying to force it into a hook produces a rule that fires on the wrong thing and gets switched off within a fortnight.

Where does the hook configuration live?

In a hooks block inside a settings file, and which file you pick is a scope decision rather than a technical one.

File Scope Shareable
~/.claude/settings.json Every project on your machine No
.claude/settings.json One project Yes, commit it
.claude/settings.local.json One project No, stays out of git
Managed policy settings Whole organisation Yes, admin controlled

Put team rules in .claude/settings.json so they arrive with the repository. Put your own habits in ~/.claude/settings.json. Running /hooks inside a session lists everything currently configured, grouped by event, which is the fastest way to find the hook somebody added and forgot to mention.

The hook worth keeping: start every session in the right quarter

An agent that has no idea your priorities changed in August will produce a beautifully argued document about the wrong thing, and it will not hedge. This is the highest-frequency failure in product work with agents, and it is a SessionStart hook.

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "cat docs/product/this-quarter.md"
          }
        ]
      }
    ]
  }
}

Text the command writes to stdout is added to Claude's context, so this-quarter.md becomes the first thing every session knows. Keep that file short and decision-shaped: the three outcomes this quarter commits to, the two things explicitly deferred, and the open questions that have owners. Ten lines that change every quarter beat two hundred that nobody updates.

Add a second entry with a matcher of compact and the same text re-injects after the conversation gets compacted, which is exactly when a long session starts drifting back to whatever it inferred at the beginning.

What does the blocking version look like?

The other half of the pattern is PreToolUse, which runs before a tool call and can stop it. A hook that exits with code 2 denies the call, and Claude Code passes whatever the hook wrote to stderr back to the model, so the deny arrives with a reason attached.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/protect-signed-specs.sh"
          }
        ]
      }
    ]
  }
}

The script behind it reads the target path from the JSON on stdin and exits 2 if the file sits under a folder of specs that have already been signed off, printing a line like "this spec is approved, open a change request instead" to stderr. That sentence is the part that matters. A silent block produces an agent that tries four variations of the same edit; a block with a reason produces an agent that does the right thing on the next turn.

Two details decide whether the block works. The script must exit with code 2: the hooks reference warns that, without valid JSON output, exit code 1 is treated as a non-blocking error and the edit goes ahead. And PreToolUse only fires when Claude calls a tool, so a file you pull into the prompt with an @ reference skips it entirely; the reference points to a Read deny rule for that case.

Builders Camp's Building with Claude Code practical challenge asks for exactly this, in a governance exercise built around a fictional scenario: a team shipped a broken deploy because nobody had defined what "done" meant for the agent working on it. The brief caps each governance component at three bullet points, deliberately, to force a decision about which control actually matters rather than listing every check anyone can imagine.

What is the third hook, once those two are running?

An audit line. A PostToolUse hook on the same Edit|Write matcher, appending the changed file and a timestamp to a running log, gives you something no agent transcript gives you: a list, in one file, of every document the agent touched this month. When a stakeholder asks who changed the pricing spec and when, the answer takes four seconds instead of an afternoon of scrolling.

That log is also the input to the improvement loop. Read it at the end of a sprint and the pattern is usually obvious: the same three files keep getting rewritten, which means either the spec template is wrong or the decision underneath it was never actually made. The glossary entry on AI workflow automation covers the wider version of this loop, where the log feeds the next iteration of the workflow rather than sitting in a folder.

Keep the log append cheap. It runs after every matching tool call, so a single line written with a shell redirect is right and a script that opens a network connection is not.

How do you keep a hook from becoming the thing that breaks?

By keeping the matcher narrow and the command fast. A matcher of Edit|Write fires on file changes. An empty matcher fires on everything, which is fine for a notification and a bad idea for anything that inspects or blocks. Every matching hook runs on every matching event, so a slow command in a broad matcher taxes the whole session.

Three habits that keep a hook set healthy:

  • One hook, one job. A hook that both logs and validates gives you nothing to disable when the validation turns out to be wrong.
  • Fail loudly at first, then narrow. A hook that silently allows everything through is indistinguishable from a hook that is not installed.
  • Review the set quarterly, the same way you review the priorities file. Hooks accumulate, and an unreviewed hook enforcing last year's standard is worse than no hook at all.

What hooks will not do for you

A hook has no judgement. It fires on an event and runs a command, which makes it excellent at "never touch this path" and useless at "check whether this spec is good." For the judgement layer, the glossary entry on quality gates covers where the automated check ends and the human review starts.

There is a second limit worth stating plainly: a hook is a shell command running with your credentials on your machine, and hooks committed to a repository arrive with that repository. The hooks reference's own security section says command hooks "execute shell commands with your full user permissions" and tells you to review and test every hook command before adding it. Never paste a hook you found online without reading what it runs. Read the hooks block of a project you did not write before you trust the folder, with the same attention you would give a script somebody asked you to run. The convenience and the risk come from the same property, which is that a hook does not ask.

Builders Camp teaches the governance layer directly. Building with Claude Code lists hooks, CI/CD and quality gates among the six topics on its public curriculum, alongside CLAUDE.md and context architecture and multi-agent orchestration, taught by Guilherme Salgueiro across 2 live sessions. Automate Workflows with AI takes the same discipline outside the coding agent, into workflow mapping, human-in-the-loop approvals and the monitoring that keeps an automation dependable, across 2 live sessions. Both sit in the AI Agentic Builders Expert Track.

The hook you delete is the one that taught you the most

A hook that survives six months is usually the boring one: the priorities injection, the path guard, the log line. The interesting hooks, the ones that try to score a document or judge whether a change is risky, tend to get removed within a month, and that removal is the useful output. It tells you the thing you wanted to automate was a decision, and decisions are what you kept the job for.

See the Building with Claude Code bootcamp for the governance module, or Automate Workflows with AI if the repetitive work you want to kill lives in your calendar rather than your codebase.

Bootcamps referred in this Guide

Frequently asked questions

What are Claude Code hooks?

Commands, HTTP requests, MCP tool calls, prompts or subagents that Claude Code runs automatically when a session reaches a lifecycle event, such as before a tool call, after a file edit or when a session starts. They are configured as JSON in a settings file, and unlike an instruction in CLAUDE.md, a hook fires every time its event and matcher match.

Why does my blocking hook not block anything?

Check the exit code. Claude Code's hooks reference says exit code 2 is the code that blocks for most events; without valid JSON output, exit code 1 is treated as a non-blocking error and the action goes ahead, even though 1 is the usual Unix failure code. A policy hook should use exit 2.

Where do I put a hook so my team gets it?

In the hooks block of .claude/settings.json inside the repository, which can be committed. Use ~/.claude/settings.json for hooks that follow you across every project, and .claude/settings.local.json for personal hooks in one project that should stay out of version control.

Do I need to write a script, or can a hook be one line?

One line is fine. A hook is a shell command, so cat priorities.md or a single jq call both work. Move to a script file when the logic needs more than one command or you want it reviewed like code.

How does a hook block something instead of just reacting to it?

A PreToolUse hook that exits with code 2 denies the tool call, and Claude Code shows Claude whatever the hook wrote to stderr. That stderr text is where you explain what to do instead, so the agent can correct rather than retry blindly.

What happens if two hooks fire on the same event?

Every matching hook runs, and they run in parallel. For PreToolUse decisions the most restrictive answer wins, in the order deny, defer, ask, allow, so one hook returning deny does not stop the others from executing their own side effects.

Can a hook add information to the session rather than block something?

Yes. Plain text a hook writes to stdout is added to Claude's context. A SessionStart hook is the usual place for this, and a matcher of compact makes it re-inject that text after the conversation is compacted.

Is a hook safer than writing the same rule in CLAUDE.md?

It is more reliable, not inherently safer. CLAUDE.md is context the model reads and generally follows; a hook is a command Claude Code runs regardless of what the model decided. Anthropic's own memory documentation says to use a PreToolUse hook when an action must be blocked rather than discouraged.

What is the risk of running hooks?

A hook is a shell command executing with your credentials on every matching event, including hooks that arrive in a repository you cloned. Read a project's hooks before you trust the folder, and keep matchers narrow so a hook fires on the events you meant and nothing else.

Sources

Written by

Andre Albuquerque

Andre Albuquerque

CEO of Builders Camp, SuperOperator, and other companies. Building products.

CEO of Builders Camp, SuperOperator, and other companies. Building products.

LinkedInMore guides by Andre Albuquerque
Guilherme Salgueiro

Guilherme Salgueiro

Builder and AI systems practitioner. Guilherme helps developers, PMs, and founders move beyond prompting into structured AI system design -- building with Claude Code, agents, and automation pipelines to ship products faster and more reliably.

Builder and AI systems practitioner. Guilherme helps developers, PMs, and founders move beyond prompting into structured AI system design — building with Claude Code, agents, and automation pipelines to ship products faster and more reliably.

LinkedInMore guides by Guilherme Salgueiro

Last updated 2026-09-27

Researched from Builders Camp's bootcamp, track and masterclass material and the sources listed on this page, drafted with AI, and fact-checked against every source cited.

See the Building with Claude Code bootcamp