Back to Research

Claude Code hooks that earn their place in a repo

Claude Code hooks turn a convention the model might follow into a rule it cannot skip. The four we see teams keep, and the ones they regret.

Maanlicht, Champigny Moonlight Champigny, landscape painting by Charles-François Daubigny (1867).
Rogier MullerAugust 15, 20263 min read

Every team we train eventually notices the same gap. They wrote the convention in CLAUDE.md, and it gets followed most of the time. Most of the time is fine for style. It is useless for anything where a single miss costs you an afternoon.

Claude Code hooks close that gap. They are commands registered against events in the session lifecycle, defined in your settings files. The harness runs them. The model is not consulted. That difference is the entire point, and it is why a hook is worth ten paragraphs of instructions.

The Claude Code hooks that survive

  • Format after write. Run your formatter on whatever file was just edited. Kills diff noise permanently and removes any need to describe formatting in prose.
  • Typecheck after write, feed errors back. The agent finds out it broke the build during the task rather than after you review it.
  • Block edits to generated and vendored paths. Migration output, lockfiles, generated clients. The agent will otherwise hand-patch a generated file and produce a change that vanishes on the next build.
  • Inject volatile context on prompt submit. Current branch, current on-call incident, whether the local database is seeded. Facts that change hourly do not belong in a markdown file.

Notice what these have in common. Each replaces a sentence that a human would otherwise repeat in review, forever.

What a blocking hook looks like

Hooks read a JSON payload on stdin describing the event. For a hook that runs before a tool call, exiting with code 2 blocks the call, and whatever you wrote to stderr goes back to the model as the explanation. Everything else about the script is ordinary shell.

#!/bin/bash
path=$(jq -r '.tool_input.file_path // ""')
if [[ "$path" == *"/generated/"* ]]; then
  echo "That file is generated. Edit the schema in proto/ and run make generate." >&2
  exit 2
fi

The message matters more than the block. A hook that says "not allowed" produces an agent that tries the same edit twice more, then works around you. A hook that names the correct path produces an agent that does the right thing on the next turn.

Where hooks go wrong

Slowness is the killer. A hook that runs a two-second check after every single edit makes the whole session feel broken, and someone will quietly delete it. Scope post-edit hooks to the file that changed, not the project. Save the full suite for the end of the session.

Then there is trust. Hooks run as you, with your shell and your credentials, on paths supplied by a model. Quote every variable. Do not build a command string by concatenating a file path. A hook is code you are choosing to run automatically, and it deserves the same review as anything that touches your machine.

The last one is scope confusion. A hook in your personal settings works only for you. Team rules go in the committed project settings file, and if you skip that step you will spend a week explaining why a rule holds on your laptop and nowhere else.

What to do next

Take the last five pull requests your team reviewed. Find the comment that appears more than once. If it is mechanical, write it as a hook this week, commit it, and stop typing it. Start with the formatter, because it is the one nobody argues about.

If you want help putting this into practice, talk to us.

Related training topics

Related research

Ready to start?

Transform how your team builds software.

Book a 15-minute sync