← Richard Murillo

BLOG

An instruction asks. A hook checks.

In early June 2026, I was landing a pull request whose main finding was that passive rules don't change what a coding agent does. While I was doing it, a hook blocked my agent's commit.

The repo's false-completion gate had decided the commit was unverified, and it stopped it. All I kept of that moment is one line in the pull request: the gate enforced the rule while the PR was landing. Meanwhile 252 KB of always-on prose rules had changed nothing measurable. A script stopped the agent. A pile of sentences did not.

That is the whole argument of this post. One more thing before I make it: the numbers behind it come from a small study, and I'll say how small.

The loop you are probably in

Your agent skipped a rule in CLAUDE.md, so you bolded it. It skipped it again, so you repeated it at the bottom. Then you added three more rules to cover the gaps. The file got longer and the agent kept skipping.

The instinct is understandable. The rule is right there, so the agent must have chosen to ignore it. But an instruction is a request. Nothing happens when the agent declines it. That is a different thing from a check, which runs whether or not the agent was paying attention.

I wanted to know what instructions buy you, so I tested it.

The test

In early June I ran a clean-room A/B in ai-agents, written up in this memory file and in PR #2499.

The control arm got no repo instructions. The treatment arm got the repo's own 34 KB copilot-instructions.md.

What happened

Both arms scored 112 of 120 hidden tests. The same 8 It.Ref cases failed every time, in both arms. The instructions did not move correctness at all.

They did move the route. The control arm took about 50 turns. The treatment arm took about 29. That is about 42% fewer turns to reach the same result. I'm leaving cost out of this post on purpose. The claim here is about how fast the agent reached the answer, and nothing else.

So the repo's own instructions were not wasted. They made the agent faster. They did not make it more correct, and the ceiling sat where the model and the spec put it.

The small N, plainly

The control arm had 3 runs. The treatment arm had 2. Temperature was 0, and the outcomes were deterministic: the scores repeated exactly. That makes the repeats signal, not noise. It does not make five runs on one task a general result. It is one task in one repo. Read it as one controlled observation, not a law.

I also ran a second regime, a refactoring kata, to test generic software engineering rules. Adding 56 KB of design rules (clean architecture, refactoring, domain-driven design, code quality) to the Sonnet arm produced identical scores: 8 of 8 on the easy version and 14 of 14 on the hard one. Haiku with no rules also scored 14 of 14. Both model tiers already solved that task at the ceiling, so the rules had nothing to add.

The rules that loaded and weren't supposed to

One finding embarrassed me. I had written rules that were supposed to apply only to certain file types. The .claude/rules files used applyTo and alwaysApply in their frontmatter. Those are GitHub Copilot and Cursor keys. Claude Code scopes rules with paths, and it ignores the others. A rule with no paths loads every session, so all 252 KB of them did.

I fixed it in early September with PR #5496. The more useful part is what came with the fix. A script, check_rule_scope_keys.py, now fails the build if a rule in that tree uses applyTo, globs, or alwaysApply.

The rule that said "scope your rules" had been an instruction. The thing that kept it true was a check.

Where the hook fits, and where my evidence stops

I want to be exact about the hook, because it is the claim I have the least data for.

The A/B tested prose. I did not run a hook arm. What I have for the hook is the observation I opened with: the gate blocked an unverified commit while 252 KB of prose rules changed nothing measurable. That is one event, not a trial. It points the same way as the A/B, and I'd bet on it, but I'm not going to call it a measured effect.

I can tell you what the gate did, because the source is public at the commit that merged the PR. It watched for git commit, gh pr create, and gh pr merge. If the commit message or PR text claimed "done" or "fixed", it looked in the session log for a test run, a build check, or a PR check. No evidence meant exit code 2, and the tool call was blocked.

An agent can read "verify before you claim completion" and still claim completion. It cannot read the gate and skip it.

The price of a gate

You will not find that hook on main today, and the reason is the other half of the story.

In August I retired every tool-call hook in the repo, in PR #5172, governed by ADR-097. The reason was speed on my own machine. Each hook is a Python process spawned on a tool call. On Windows a Python cold start costs about 250 ms, and more under Defender's real-time scanning. I had already consolidated the spawns once and it was still too slow for me.

The pull request says plainly what that removed: real protection, including for people who install the plugin. A sub-agent spawn with no model named can silently inherit the session model again. A Serena write can land in the wrong worktree again.

There is also the failure a gate can cause. An earlier bug, issue #5013, is cited in the ADR: a wrong deny on a hot matcher blocked 127 unrelated Bash commands over 21 minutes before I caught it. A wrong instruction is ignored. A wrong gate stops your work.

So a gate is not free. It is code you have to keep correct, and you pay for it on every call. I decided that for tool-call hooks, on my machine, the bill was too high.

What I kept is the same idea one step later. Git hooks and CI still check what gets committed, and the scope-key check runs in the build. The principle is to put the check at the cheapest point that still blocks.

What I'd do with this

If your agent skips a rule, don't write a louder rule. Ask what would make skipping it impossible.

  1. Name the action. "Claims done without proof" is a vague worry. "Runs git commit with 'fixed' in the message and no test run in the log" is something a script can check.
  2. Make the check blocking. An advisory warning is another instruction.
  3. Choose where it runs. A pre-tool hook, a git hook, a CI job, and a build step all block. They differ in cost and in how early they catch the mistake.
  4. Keep the prose small. My 34 KB of repo-specific instructions made the agent faster, so I'd keep those, tight and exact. My 56 KB of generic design rules scored the same as none.
  5. Test the check. Run it against the thing it should stop. A gate you have not seen block anything is another hope.

Matt Kotsenas said this first

Matt Kotsenas wrote the principle in The Control Paradox, in February: "Agents will claim success without evidence. Make verification blocking, not advisory. No PR without passing tests, no 'done' without proof." He also asked toolmakers for a "hermetic mode," a profile that isolates a session from machine state like user instructions. My headless run from outside the repo is a crude version of that mode, built by hand.

I'd say yes, and. This is a controlled comparison behind that principle. Prose changed how fast the agent got there. Verification changed what it was allowed to do. If you read his post for the principle and mine for the numbers, you have both halves.

How this could be wrong

Here is the claim in a form you can break.

I said in my capability post that I would publish numbers, including the ones that go against me. This is the first batch, and it is about instructions, not skills. The same idea shows up in Move the deployment failure to the keystroke: the best place to catch a mistake is where it gets made.

What rule is your agent skipping right now? If you have a gate that stopped something a rule never did, or a case where the extra prose helped, open an issue on ai-agents and tell me.

Written by Richard Murillo. Find me on GitHub rjmurillo, LinkedIn richardmurillo, X @OODARichard.