The instruction file feels like control

You have written a careful CLAUDE.md, or the equivalent instruction file for whatever agent you run. It states the rules plainly: never touch this directory, always run the tests before claiming a task is done, ask before deleting anything. The agent reads it every session. Most of the time it follows it. That feels like control, and for a single short session, it mostly is.

It stops feeling like control the moment a build runs unattended for longer than an afternoon.

The probabilistic reality

An instruction file is a prompt, and a prompt is read probabilistically, not enforced deterministically. Anthropic's own documentation is candid about this: instruction-following has no guarantee of compliance, and the guarantee gets weaker exactly where you need it most, on rules that are vague or that conflict with each other. Three drift modes show up often enough in practice to name individually. A rule gets ignored mid-execution because something else in the moment felt more pressing. A rule gets forgotten as the context window fills up and eventually compacts, taking detail with it. A rule gets skipped because the model judges, in that specific instance, that it does not apply. There is a documented case of an agent asked directly whether it had followed a rule, replying that it could read the rule and recite it back word for word, and then continuing to not follow it.

None of that makes the instruction file worthless. It makes it the wrong tool for anything that must never fail.

Three tiers, not one

Treat agent governance as three separate tiers, and never let anything load-bearing rely on tier one alone.

Tier one is advisory. CLAUDE.md and skills both live here. A skill is a folder with a SKILL.md file that the agent loads on its own when a task description matches, which is genuinely useful for procedural knowledge you do not want to keep re-explaining every session without permanently spending context on it. But a skill still teaches. It shapes what the model is likely to do. It does not change what the model is able to do, and that distinction is the whole tier.

Tier two is hooks. A hook is a shell command the agent's own runtime executes at a fixed point in its lifecycle, receiving the event as JSON on stdin and answering with an exit code, entirely outside the model's own judgement. This is enforcement rather than persuasion, and it is where anything that must never happen actually has to live.

Tier three is gates. CI blocking a merge until a check passes, which only counts as a control once it has been proven to fail on a deliberately planted defect. A three-agent, 72-hour autonomous build we ran recently produced a version of this failure mode at real scale: a CI assertion confirmed every one of 80 database tables had a security policy attached, stayed green for days, and turned out to be verifying something almost entirely disconnected from whether any of those policies could ever be evaluated. The full story is worth reading on its own: how a CI check that never failed missed a security hole a login attempt found in seconds.

The hook mechanics that actually matter

PreToolUse fires before any tool call executes. Exit 0 and the call proceeds. Exit 2 and the call is blocked, with the stderr message handed back to the agent as the reason it was refused. Three details catch people the first time they wire one up.

Exit 1 does nothing useful. It reads as the natural choice, since it is the conventional Unix failure code, but Claude Code treats it as a non-blocking error and the action proceeds anyway. Anything security-critical needs an explicit exit 2. A structured JSON decision only parses when the hook exits 0, so a block decision printed as JSON and followed by exit 2 gets discarded, and the agent only ever sees the plain stderr text, which is where the real reason has to be written. And blocking is not the same everywhere: PreToolUse denies a call outright so it never runs at all, while a Stop hook exiting 2 prevents the current turn from ending and hands the agent a reason it can still reason its way past, which makes it a strong suggestion rather than a wall.

That last distinction decides where a rule belongs. Anything that must never happen under any circumstance goes in PreToolUse. Anything that is a strong preference the agent should reconsider, but might occasionally have a legitimate reason to override, can live in a Stop hook instead. Confusing the two is how a team ends up with a rule they believed was absolute, discovering only afterwards that it was a suggestion the whole time.

Where this leaves the instruction file

Keep CLAUDE.md. It is still the fastest way to teach house conventions, and most of what an agent does in a session is exactly the kind of judgement call an instruction file is good at shaping. Just stop asking it to be the reason a security boundary holds, because a rule that only lives in a document the agent reads is a rule the agent can, on a bad day inside a long compaction cycle, fail to follow and still believe it did.

The harder discipline sits one tier up from hooks, in how you prove a gate is actually catching anything rather than passing by accident. That failure mode, a check that has never once failed, is its own separate problem, covered here: why a control that has only ever passed is not a control.