Claude Code Hooks: The Safety Layer Most Engineers Skip
Claude Code Hooks are deterministic callbacks that run before and after every agent action. They are the difference between an AI tool you can trust in production and one that occasionally does something you did not ask for.
The most common concern I hear from engineering leaders about AI coding agents is not that they are too slow or too expensive. It is that they cannot be trusted to do the right thing consistently. Engineers have heard enough stories about agents deleting tests to make CI green, overwriting files they should not touch, or making decisions that looked locally correct but broke something upstream.
Claude Code Hooks are the answer to that concern. They are a deterministic layer that runs before and after every action the agent takes. They are not probabilistic. They do not rely on Claude understanding your intent. They are shell commands that execute reliably, every time, regardless of what Claude decides. That is not a limitation. That is the point.
Most teams using Claude Code have never configured a Hook. That is a significant gap. This post explains how Hooks work, the three types you should configure first, and how to use them to build a system you can trust.
What a Hook Actually Does
A Hook is a shell command that Claude Code runs in response to specific events in the agent's workflow. The command runs outside of Claude's decision-making process. Claude cannot choose to skip it. It cannot be talked out of running it. If the command exits with a non-zero code, the action is blocked.
This is architecturally different from prompting Claude to be careful. Prompts are probabilistic. You can tell Claude not to delete test files, and it will follow that instruction almost all the time. A Hook that blocks any shell command touching your test directory follows that instruction every single time. The reliability is categorically different.
There are three Hook types that matter for most engineering teams:
PreToolUse: Runs before Claude executes a tool call. This is where you block actions before they happen. A PreToolUse Hook can inspect the proposed command, the file being written, or the shell operation being attempted and return a non-zero exit code to prevent it.
PostToolUse: Runs after Claude executes a tool call. This is where you validate that what Claude did meets your expectations. A PostToolUse Hook can check that the files Claude modified still pass your linting rules, that tests still run, or that specific invariants in your codebase still hold.
Notification: Runs when Claude sends a notification event. Useful for logging, alerting, or triggering downstream processes.
PreToolUse is where most of the safety value lives. The ability to inspect and block before an action happens is what turns an unpredictable agent into a controllable one.
The Three Hooks Your Team Should Configure Today
Not all Hooks require careful design. Three of them are straightforward to configure and provide disproportionate protection against the failure modes that actually happen.
A commit message validator. Claude Code writes commit messages as part of many workflows. Without constraints, the messages reflect whatever Claude thinks is a reasonable summary, which may or may not match your team's convention. A PostToolUse Hook on git commit that checks the message format against your standard, and blocks the commit if it does not match, costs about ten lines of shell script and eliminates an entire class of inconsistency from your repository history.
A protected path blocker. Most codebases have files that should never be modified by automated processes: environment configuration, secret management files, lock files in specific states, production database migration files. A PreToolUse Hook that checks any Write or Edit operation against a list of protected paths and blocks it unconditionally takes fifteen minutes to set up and prevents a category of accidents that would otherwise require a rollback.
A pre-action notification log. Before deploying to any environment or running any migration script, a PreToolUse Hook that logs the proposed action, the timestamp, and the session context creates an audit trail. This is not primarily about catching problems in real time. It is about having a record when something unexpected happens and you need to reconstruct what the agent did.
Configuring Hooks in CLAUDE.md
Hooks are defined in the CLAUDE.md file, which Claude Code reads at the start of every session. The configuration is straightforward:
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: "command"
command: "scripts/sec.sh"
timeout: 5
The matcher field determines which tool calls trigger the Hook. "Bash" matches any shell command. "Write" matches file write operations. "Edit" matches edit operations. You can be as specific or as broad as needed.
The command field points to a script in your repository. Keeping the logic in a named script rather than inlining commands in the CLAUDE.md makes the Hooks maintainable and reviewable. A Hook that blocks dangerous operations should itself be reviewed.
The timeout field prevents Hooks from stalling the agent indefinitely. For safety-critical Hooks that should block immediately or not at all, a short timeout is the right setting.
Exit Codes Are the Control Mechanism
The exit code of a Hook command determines what happens next. This is the part of Hooks that most teams do not initially understand, and it is where the real control lives.
Exit code 0 means the Hook passed. Claude continues with whatever it was doing.
Exit code 2 means the Hook blocked the action. Claude receives a message that the action was blocked and can respond, but the action does not execute.
Any other non-zero exit code also blocks the action. The convention of using 2 specifically for intentional blocks allows your scripts to distinguish between "we deliberately blocked this" and "something went wrong in the Hook script itself."
This exit code pattern means your security script can encode complex logic: check the command against a list of patterns, inspect the file being modified, query your internal API for authorisation, and then return 0 or 2 based on the result. The logic lives in your code. The enforcement is deterministic.
Hooks Are Not a Substitute for Good Prompts
An important clarification: Hooks are not the right mechanism for shaping Claude's behaviour in general. They are the right mechanism for enforcing hard constraints.
The distinction matters in practice. If you want Claude to prefer a certain pattern, write that in your CLAUDE.md context or your Skills. If you want Claude to consistently use TypeScript over JavaScript when creating new files, put that in your system context where Claude can reason about it and apply it appropriately.
Hooks are for rules that must be enforced regardless of context: files that must never be touched, operations that must always be logged, commit formats that must always match your standard. If the rule has no legitimate exceptions, it belongs in a Hook. If it is guidance that a senior engineer would sometimes override with good reason, it belongs in context.
Getting this distinction right keeps your Hooks focused and your system understandable. A Hook library that tries to encode every team preference quickly becomes unmaintainable. A Hook library that encodes only the hard constraints stays small, fast, and trustworthy.
Hooks in Production Workflows
The most valuable Hooks in practice are the ones that protect production-adjacent operations: deployment scripts, database migrations, infrastructure changes. These are also the operations where most teams never configure Hooks because the operations are rare and the cost of a mistake is high.
That combination, rare but high-cost, is exactly where deterministic enforcement is most valuable. A Hook that requires a manual confirmation token before any command matching a deployment pattern runs does not add friction to daily development. It adds one extra step to deployments, which is where one extra step is warranted.
Teams that have adopted this pattern consistently report the same thing: the Hook almost never fires in a disruptive way, because developers do the right thing almost all the time. What it does is provide certainty: the category of accidents where an agent runs a production deployment in a context where that was not intended becomes structurally impossible rather than merely unlikely.
Building Trust in Agent Systems Through Determinism
The broader principle behind Hooks is worth naming explicitly. AI agents are probabilistic systems. They are good at following instructions, but "good" means something short of "always." For the vast majority of tasks, probabilistic is fine. For a small category of operations, probabilistic is not acceptable.
Hooks give you a mechanism to define exactly which operations fall into that second category and enforce your constraints deterministically. This is what makes it possible to deploy agents in contexts where the stakes are higher than autocomplete suggestions.
The teams that have moved furthest with Claude Code in production have all, without exception, built a Hook layer before running agents in critical workflows. Not because they distrust Claude's judgement in general. Because they understand the difference between probabilistic and deterministic, and they have correctly identified which category specific operations belong in.
That is not excessive caution. That is good engineering applied to a new type of system.
I help engineering teams close the gap between "we use AI tools" and "AI actually changed how we deliver." Book a 20-minute call and I'll tell you where the leverage is.
Working on something similar?
I work with founders and engineering leaders who want to close the gap between what their technology can do and what it's actually delivering.