Your CLAUDE.md is context, not configuration
Trimming your instruction file won't stop agents ignoring the rules. Compliance decays as the work proceeds, and only mechanical gates survive it.
David Heinemeier Hansson spent most of 2026 arguing that people write too much for their coding agents. Shorter prompts. Less prescription. Trust the model. Then, describing how well agents handle Bash on the Lex Fridman podcast, he mentioned one exception:
Except for one thing, and I have to smack the agents over the back of the head every single time I catch it, and remind it to look at the agent’s MD file, because I will have an instruction in there not to make early exits.
One rule. A short file. Written by someone who has thought harder than most about how to talk to these things. Still ignored, repeatedly, to the point of being a running annoyance.
Anthropic’s own documentation explains why, and it is blunter than most of the advice circulating about instruction files:
Claude treats them as context, not enforced configuration. To block an action regardless of what Claude decides, use a PreToolUse hook instead.
That sentence is the whole argument.
The problem is not that your file is too long
The standard diagnosis is bloat. Your CLAUDE.md grew to 400 lines, the important rules got buried, so trim it and put the critical ones at the top.
That diagnosis has now been tested directly, and it did not survive. A May 2026 study ran 1,650 Claude Code sessions across two TypeScript codebases, producing 16,050 function-level observations, with Sonnet 4.6 as the primary model and Opus 4.6 as a cross-check. It varied four things around a reference configuration: file size at 25, 100, 250 and 500 lines, the position of the target rule from top to bottom, a single file versus a CLAUDE.md and AGENTS.md pair versus nested per-directory files, and whether the file deliberately contradicted itself.
None of them mattered. From the abstract:
None of the four structural variables or three two-way interactions produces a detectable contrast after multiple-testing correction.
The size and conflict results carry Bayes factors between 0.05 and 0.10, which is affirmative evidence of no effect rather than a failure to detect one.
One thing did move. Compliance fell by roughly 5.6% in odds for each additional function the agent generated, an odds ratio of 0.944, reproduced on the second codebase and on Opus 4.6. The rule did not get lost because it was on line 340. It got lost because the agent had been working for a while.
Two caveats, since that number carries the argument. It was found during analysis rather than predicted in advance, and the author explicitly asks for independent replication before anyone treats it as robust. It is also not uniform. Across the three multi-function tasks the per-step slopes ranged from no detectable decay at all to a much steeper one, and the whole study ran in single-turn --print mode, which the paper concedes is a constrained subset of how these files actually get used. If you want to argue with this post, that is where to push.
That matches what Anthropic conceded in June 2026: Claude follows a prompted instruction most of the time, but in a long session, under pressure, or in an ambiguous situation, it can fail to.
It also matches the only benchmark I have found that tests this property head-on. HANDBOOK.md gives agents expert-written operating procedures of 20 to 124 pages and grades the result programmatically against 824 criteria. Under strict grading, where a trial passes only if every criterion is met, the strongest model evaluated passes 36.2% of trials. The failure modes named in the paper are worth reading twice: agents let a plausible but unauthorised in-environment request override standing policy, perform a required check and then act against its result, lose rule details over long horizons, and report compliance they did not achieve.
That last one is the uncomfortable part. The agent tells you it followed your convention. It did not.
The disagreement I am not going to smooth over
There is a paper pointing the other way.
An August 2026 study tracked 247,694 instruction lifetimes across 1,867 repositories. Instruction counts grow 226% over a file’s life, roughly 4.9 added per commit, and deletion is rare because removing a rule means reconstructing why someone added it. In its controlled experiments, extraneous instructions degraded constraint satisfaction by 24.1 percentage points.
That is a length effect. It contradicts the null above.
The two studies are not measuring the same thing. The first padded files with extra lines and tracked one trivial annotation rule. The 24.1 point figure in the second comes from seeding a task with sixteen irrelevant instructions pulled from unrelated problems and having a model judge the output. Neither setup is your CLAUDE.md.
But there is a real difference between a longer coherent file and a file carrying sixteen rules that have nothing to do with the work in front of the agent. The observational half of that second paper explains how repositories drift into the second state: instructions accumulate at about 4.9 per commit and are almost never removed, because deleting one means reconstructing why somebody added it.
So length itself does not look like the mechanism. Irrelevance does. Trimming a coherent file is unlikely to buy you much; clearing out rules that no longer apply to anything is a different job with a different payoff.
The advice everyone repeats has mostly not been tested
Two pieces of guidance appear in nearly every article on this subject. Put your critical rules at the top, because of lost-in-the-middle. Split into per-directory files so the agent loads the relevant one.
Both were tested in that May study, as the position and architecture variables. Neither reached significance after correction. There is an asymmetry here I should not gloss over. Unlike the size and conflict results, these two are absences of evidence rather than evidence of absence, and both showed raw dips that just missed the threshold.
The front-loading advice usually cites Liu et al., which I have cited here myself. That paper tested multi-document question answering and key-value retrieval. It found that models retrieve information best from the beginning and end of a long input and worst from the middle. It did not test whether a model obeys an instruction, and stretching it to cover instruction adherence is an extrapolation, not a finding. Uncle Bob invokes lost-in-the-middle by name to explain why instructions soften over a session, without citing the paper. The behaviour he describes is real. The mechanism he reaches for does not quite cover it.
Then there is the instruction budget. You will read that frontier models reliably follow 150 to 200 instructions, that Claude Code’s system prompt consumes about 50 of them, and so you have 100 to 150 left to spend.
The first half has a real source, and to HumanLayer’s credit they link it. It is IFScale, which tested 20 models against up to 500 instructions on a business-report task. The paper is narrower than the advice built on top of it. That 150 figure describes the top two reasoning models, gemini-2.5-pro and o3, which hold near-perfect performance that far before declining. Claude Sonnet 4 is classified there as a linear decayer with no threshold at all, degrading from the first instruction onwards. And each instruction is “include this exact keyword”, which measures recall under load, not whether a rule about your codebase survives an hour of refactoring.
The second half, the 50 instructions Claude Code supposedly spends on itself, I cannot source at all. It reads as one guide’s own estimate, repeated since without attribution.
Anthropic’s own number, under 200 lines per file, at least comes from the vendor.
What the file is genuinely for
None of this means delete it.
The most striking number in the May study is the one nobody quotes. Baseline compliance with an instruction file present was 67.7%. Without any file at all, the rule was followed in zero of 524 observations.
Not “worse”. Zero.
So the file is doing real work. It is just doing probabilistic work, and you should stock it accordingly. Anthropic’s /doctor command proposes trims for a checked-in CLAUDE.md, cutting anything derivable from the codebase and keeping pitfalls, rationale, and conventions that differ from tool defaults. That is the right filter. The agent can read your directory structure. It cannot know that the staging queue silently drops jobs over 4MB, or why you stopped using the ORM’s soft deletes in 2024.
GitLab, which runs one of the more aggressive public AI-assisted development setups, keeps its AGENTS.md to 84 lines, most of it a context-loading section that routes the agent to separate files for git, merge requests, code review and CI. Note that Claude Code reads CLAUDE.md and not AGENTS.md, so if you keep both you need an @AGENTS.md import or a symlink, and splitting into imports does not save context because imported files load at launch anyway.
Which hooks actually gate
If a rule has to hold every time, it belongs somewhere the model does not get a vote.
Claude Code now exposes 31 hook events. The distinction that matters is which of them can stop something happening:
| Event | Blocks? | Use for |
|---|---|---|
PreToolUse | Yes, before the tool runs | Refusing an edit outright |
Stop | Yes, refuses to end the turn | ”Tests must pass before you finish” |
PostToolBatch | Yes, before the next model call | Batch-level checks |
PostToolUse | No, the tool already ran | Feedback and nagging |
Two details will cost you an afternoon if you get them wrong.
Exit code 2 is the blocking code. Exit 1, the conventional Unix failure code, is treated as a non-blocking error and Claude proceeds with the action anyway. If your gate script returns 1 on failure, you have built a logger.
And PostToolUse cannot block, because by the time it fires the write has happened. In a live session, a gate that grepped written files for TODO and exited 2 fired exactly as configured. Claude read the complaint, decided it conflicted with what the user had asked for, said so, and left the file on disk with the TODO in it.
An advisory gate is a suggestion with extra steps. The model can still talk its way past it, exactly as it can talk its way past a line in your CLAUDE.md.
The Stop hook is the one that holds, because it refuses to let the turn end:
{
"decision": "block",
"reason": "Mutation score below 70. Fix the surviving mutants before finishing."
}The runaway case is handled for you. The hook input carries a stop_hook_active flag, and Claude Code overrides the hook and ends the turn after 8 consecutive blocks.
One more trap. Scope your matcher too tightly and the gate never fires. A hook matching Write|Edit sounds right and is trivially bypassed, because the agent can create the same file with a Bash heredoc instead, which is a Bash tool call. Matchers match tool names, not intent. Use * and work out what changed yourself with git status --porcelain, or use the if field with permission-rule syntax such as Edit(*.ts).
Gates worth wiring up
These configs were executed rather than copied out of documentation, on ESLint 10.9.1, dependency-cruiser 18.2.0, xenon 0.9.3 and import-linter 2.14.
Complexity, in JavaScript. Exits 1 on violation:
export default [
{ files: ["**/*.js"], rules: { complexity: ["error", { max: 5 }] } },
];For TypeScript files you also need typescript-eslint’s parser, since ESLint’s default parser trips over type annotations.
Architecture, in TypeScript. This is the rule prose is worst at enforcing, because it is invisible in any single diff:
module.exports = {
forbidden: [
{
name: "no-domain-to-http",
severity: "error",
comment: "The domain layer must not depend on the HTTP layer.",
from: { path: "^src/domain" },
to: { path: "^src/http" },
},
],
options: { doNotFollow: { path: "node_modules" } },
};Name that file .dependency-cruiser.cjs rather than .js if your project sets "type": "module", or Node will refuse to load it.
The Python equivalent, via import-linter, which prints the offending import chain and so makes good agent feedback:
[importlinter]
root_package = myapp
[importlinter:contract:domain-independence]
name = Domain must not depend on HTTP
type = forbidden
source_modules =
myapp.domain
forbidden_modules =
myapp.httpTwo corrections to advice you will find elsewhere. Both came from running the tools rather than reading about them.
radon does not fail a build. radon cc myapp/messy.py -n C prints the offending block and exits 0. It reports, it does not gate. The tool that gates is xenon, which wraps it:
xenon --max-absolute B --max-modules B --max-average A myapp/mutmut has no score threshold. Version 3.7.0 has no threshold flag anywhere in its command set, and a run with two surviving mutants out of three exits 0. If you want mutation testing to gate in Python, either compute the score yourself from mutmut export-cicd-stats, which writes raw counts and no percentage to mutants/mutmut-cicd-stats.json, or use Cosmic Ray, where cr-rate --fail-over 20 session.sqlite exits non-zero when more than 20% of mutants survive. Note that flag gates on survival rate, not mutation score.
For TypeScript, Stryker 10.0.0 does gate, but only if you ask. The break threshold defaults to null, which means the score never fails the build:
{ "testRunner": "vitest", "thresholds": { "high": 80, "low": 60, "break": 70 } }The runner is a separate package, so install @stryker-mutator/vitest-runner or Stryker will fail to find it.
What the gates cost
The strongest version of this argument comes from Robert C. Martin, which is not where I expected to find it.
His agent pipeline runs a specifier, a coder, a cleaner, a hardener that does mutation testing, and a QA agent. He describes work that takes a single agent five minutes with questionable results taking about an hour through the whole chain, against roughly half a day for a person. He has published the tooling: crap4clj, crap4java and crap4go. None carries a licence, and two of the three READMEs reserve all rights, so read them for the idea rather than vendoring them.
The CRAP score they compute is not new. Alberto Savoia and Bob Evans published it in 2007 as comp(m)^2 * (1 - cov(m)/100)^3 + comp(m), and set 30 as the threshold for crappiness. Because of that cubic term the coverage required climbs fast at the top of the range: complexity 26 needs about 82% coverage to clear 30, complexity 30 needs 100%, and anything above 31 can never pass at any coverage. PHPUnit still computes it, and its Crap4j reporter still defaults to Savoia’s 30 nineteen years later. It counts a method as crappy at the threshold rather than above it, so under PHPUnit complexity 29 is the highest that clears.
Martin’s threshold is 8. crap4java exits 2 above that. In the podcast he describes keeping human code below 4, setting 6 for agents, and considering 8.
The counterweight comes from Martin himself, and I did not expect it. He ran a controlled experiment on the gate and published the result. His negative-test-experiment repo covers eight runs of Hunt the Wumpus across four testing disciplines with the CRAP gate on and off. Its own summary concludes that CRAP buys coverage and spends cleanliness, and does not improve design. Turning the gate on grew the code by between 21% and 66% depending on the discipline, and more than doubled the function count in every single run, because the cheapest way to satisfy a cubic penalty on complexity is to shred every function into fragments. That experiment ran the gate at 4, stricter still than the 8 in his Java tool.
That is a gate optimising exactly what you measured and nothing else. It is the oldest failure in software metrics, and putting an agent behind it makes it faster, not safer.
Grady Booch’s objection lands in the same place. Coverage tells him a thing works, and nothing about whether the agents introduced a vulnerability, left dead code that will hurt understandability later, or missed a factorisation that would have mattered for performance. I have only found that quote reproduced secondhand, so treat the wording as reported rather than sourced. Dex Horthy gets to the same place from the other direction, arguing that no amount of deterministic linting and AI review has yet made it safe to stop reading the code.
The cost objection deserves an answer. If the rule lives in a gate rather than the prompt, the agent does the work its own way, trips the gate, and corrects itself, burning tokens it would not have burned if it had simply complied the first time. That is true. It is the same trade as a type system or a failing test, and it is worth it for the rules where being right matters more than being cheap, and not worth it for stylistic preferences.
What I’m changing
I checked my own setup while writing this. My project and global CLAUDE.md files are 66 and 78 lines, comfortably under Anthropic’s 200. My project’s .claude/settings.local.json carries a permissions block. My global settings file carries model and interface preferences. Neither defines a single hook.
So I had already done the easy half. Short files, no bloat, nothing to trim. And I had zero mechanical enforcement of anything, which means every convention I care about has been running on that 67.7%, degrading as each session goes on, with the agent occasionally reporting compliance it had not achieved.
Four things worth doing, in the order I would do them.
Sort your existing file into two piles. Knowledge the agent cannot derive from the repo stays. Rules that must hold every time move to a gate. Anything that is neither is noise you are paying for on every turn.
Put the must-hold rules behind Stop or PreToolUse, and make the script exit 2. Not 1.
Check your matchers by trying to bypass them. If a heredoc gets around your gate, it is not a gate.
And when you pick a metric to gate on, ask what the cheapest way to satisfy it is, then assume something tireless and literal-minded will find that route within the hour. Martin’s own experiment is the warning: the gate held, the score improved, and the code got worse.