
Line 31 of our repository's rules file, as it stood on August 17, is a heading in capitals: "NEVER BUILD GO BINARIES WITH NAKED go build". A few lines further down, the file calls any binary built that way "an immediate revert". Line 303 says "Don't run heavy work on this laptop." The file is 640 lines and 5,866 words, and every agent that works in the repository loads it before its first turn.
Agents kept doing both. Writing the rule into the file again, louder, changed nothing, and neither did a hook that repeats both rules when a session starts. The version that holds is the one our Go harness has used for months: a small program that runs at the moment the agent tries the forbidden action, refuses it, and tells the agent what to do instead. On August 18 those two rules are still reminders, which is why they are the next ones to move.
That is the claim of this post. An instruction is read once, at the start, and has to survive everything that happens after it. A hook is read at the exact moment of the mistake. And once you steer agents with hooks, the steering layer is code, so it needs tests like any other code.
A rules file is read at minute zero
Anthropic's documentation for Claude Code says this plainly. CLAUDE.md files "are loaded at the start of every conversation", Claude treats them as context, and "to block an action regardless of what Claude decides, use a PreToolUse hook instead" (Claude Code memory docs, archived August 18, 2026). The same page says to "target under 200 lines per CLAUDE.md file", because longer files consume more context and reduce adherence.
Ours was 640 lines on August 17. Forty-eight of them contain "never", "always", "must" or "do not". The hard lines sat at the top, which is the best seat in the file. The rule about heavy work sat at line 303, between a table of project skills and a quick reference for remote boxes. Position matters: Liu and colleagues found that language models answer best when the relevant information sits at the start or end of a long input, and significantly worse when it sits in the middle (Lost in the Middle, arXiv 2307.03172, July 2023).
wc and grep. The 200-line target is from the Claude Code memory docs.Then the session runs. An agent reads 640 lines, starts work, and later in the session the context holds file contents, test output, diffs and its own plans. The rules are still in there, far up. The decision to type go build happens down here, with the most recent tokens all saying "make it compile".
We tried the obvious patch first. Since May, a session-start hook in the repository has printed a short block of hard rules into every new session, repeating both rules in a short block. It made the rules shorter and fresher at minute zero. It still fired only at minute zero, and on August 18 it is the only hook either rule has.
A hook is read at the action
In Claude Code, a PreToolUse hook sees a tool call before it executes. Exiting with code 2 blocks the call, and the hook's stderr comes back to the model as the tool's error (Claude Code hooks reference, archived August 16, 2026). The same reference says the exit-code-2 block is the one outcome a JSON reply cannot override.
That changes what the agent has to remember. Instead of carrying a rule across a long session, it reads one error message at the moment it matters, attached to the command it just tried.
Our own Go harness has steered agents this way for most of a year. One of its hooks, added in December 2025, watches for web_fetch. When the session's message history is available to it and holds no web_search result yet, it refuses the call with this message:
web_fetch blocked: You must call web_search first to find valid URLs. Guessing URLs will result in 404 errors. Example: web_search(query: "your topic") then use URLs from results.
Read it the way the agent does. It names the refused action, the reason, the next step, and an example of the next step. The error tells the agent that its next call should be a search. The hook also has a deliberate gap: when it cannot read the session's history, it lets the fetch through, and and a test pins that choice so anyone changing it sees what they are changing.
The message is half the hook
A bare denial leaves the agent stuck. It knows one door is closed and nothing about which door is open, so it tries the nearest variant: a different flag, the same command wrapped in bash -c, a script that does the same thing. Each attempt costs a turn, and some of them get through, because a pattern written for one spelling misses the next.
A denial that names the next step turns the block into a redirect. The agent takes the path it was told about, because that path is now the most recent and most specific instruction in its context.
We use a third shape too: a hook that rewrites the action instead of refusing it. Another harness hook, from October 2025, inspects shell commands before they run. When the command is a known long-runner such as npm install, cargo build or docker pull and carries no timeout, the hook adds one. The agent never sees an error, and the old form of the mistake stops being possible.
The steering layer is code now
Here is the part nobody warns you about. Once a rule lives in a hook, it can be wrong in all the ways code is wrong.
On August 3 we shipped a fix for one such bug. One of our research agents had a completion hook: before the agent may finish, it must have delegated at least once. The hook had no scope. When someone opened a session and said hello, the agent correctly answered without calling any tool, the hook refused to let it finish, the harness re-injected the same reminder three times, and the session ended as "Required completion step failed". A greeting failed a research pipeline's check. The fix skips completion hooks when a session has made zero tool calls, and it landed with a test that pins the new behavior beside the old failure.
A rules file cannot have that bug, because a rules file never stops anything. A hook stops things, so it can stop the wrong thing. The requirement that an agent find no corner to slip through has a twin: no corner where the hook fires on work that was correct.
So we treat hooks as code. On August 17 the harness hooks package held 22 source files and 4,502 lines, with 1,973 lines of tests across 30 test functions. The fetch hook has five tests. Two of them set its direction and are the pattern: one proves it blocks a fetch when no search has happened, the other proves it allows a fetch after one. Each catches a distinct failure: a hook that never fires, and a hook that always fires.
What stays in the rules file
Limits. Hooks only see actions. "Prefer the existing function over a new one" and "write in plain English" have no single tool call to match, so they stay in prose. Matching on shell strings is brittle, every hook is one more program to maintain, and a hook only binds the harness that runs it. The rules file is still where an agent learns what the project is, where things live and why a line exists.
What we stopped doing is writing a hard line into the file and expecting it to hold. When breaking a rule costs a reverted binary or a laptop running a job meant for a remote box, the rule belongs in a hook that fires on the action, with a message that names the alternative and tests in both directions. A demo can skip this because a demo sets the cost of failure to zero; production puts it back.
Where Rush applies it
Rush applies the same idea to where the work runs and how it ends. A Rush cloud run clones the repository into its own sandbox in the cloud, so a heavy test suite in a Rush run executes there. And when the work is code and the agent exits without a PR, the run ends as needs_review, waiting for a person to follow up, instead of being marked done. When a run goes wrong anyway, the next problem is finding the agent and the exact call.
Write the rule down once so the agent understands why it exists. Then put it where the mistake happens, and test it in both directions.
