Every team rule that lives only in people's heads will get broken sooner or later. "Always run prettier before committing." "Never let anything go straight to main." "Lint before declaring done." With humans this already leaked. With an agent making dozens of decisions a minute, it leaks on the first distracted session. Hooks are where those rules stop being folklore and become an executable contract.
A hook, in Claude Code, is a shell command the harness fires automatically at lifecycle
events of the session. They live in settings.json, not in the model's judgment.
That's the part that matters: the agent does not decide whether to run the hook. The harness
runs it, every time, regardless of what the model "remembered" to do. It's precisely because
they're deterministic and outside the agent's discretion that they work as guardrails.
Before: validate and block
The most powerful event is PreToolUse. It runs before a tool executes and can
block the action. A hook that exits with code 2, or returns JSON with
permissionDecision: "deny", stops the call from happening and even tells the
agent why. This is where "never let it go to main" lives: a matcher on Bash that inspects
the command and denies any git push aimed at a protected branch. It's not a suggestion
in the prompt. It's a locked door.
"A guardrail that depends on the model remembering isn't a guardrail - it's a hope."
The practical difference is brutal. Asking "please don't push to main" in
CLAUDE.md works until the session gets long enough for that instruction to drift
out of focus. PreToolUse doesn't forget, doesn't get distracted, and doesn't negotiate.
Either the command passes the filter, or it simply doesn't run.
After: format and verify
PostToolUse runs after the tool has already executed - so it doesn't block, it reacts.
The classic case is auto-format: a matcher on Write|Edit that grabs the just-edited
file and runs prettier on it. The agent doesn't even need to know it exists; the code comes out
formatted the team's way, every time. It's exactly the pattern I use here on the portfolio, with
formatting wired into a post-edit hook.
The same event works for loud verification. Edited a test file? Run the test and inject the result back into context. Touched a schema? Trigger the type-check. The point isn't to make it pretty - it's to close the loop without depending on the agent "remembering" to validate before saying it's done.
A good guardrail fails loudly and deterministically. If the only thing holding a rule in place is the model remembering it, it isn't a rule - it's an implementation detail waiting to go wrong.
Around: set the stage
There are also events that wrap the whole session. SessionStart runs when the conversation
begins and is great for loading context the agent will need anyway: the current branch, the open
issues, the state of the local database. Stop runs when the agent thinks it's done
- and can prevent it from stopping if a gate didn't pass, handing the ball back: "lint is still
failing, keep going". Before, after, and around: the three fence in the agent's autonomy without
dictating what it does in the middle.
In the end, hooks are how you encode the team's culture into a versioned file - and one of the central pieces of the harness around the agent. The rules that used to live in a Slack channel, a poorly documented onboarding, or the memory of whoever's been around longest become a contract the agent signs with no say in the matter. And a contract that doesn't depend on goodwill is the only kind that survives production.