Somewhere in the last two modules you told Claude Code how you wanted it to work. Maybe it was “keep your answers to three lines”, or “show me the change before you write it”, or “leave that file alone”. It did what you asked. Then, twenty minutes later in the same conversation, it stopped doing it.
That is not the tool being broken, and it is not you writing the instruction badly. It is worth knowing what actually happened, because the fix is not the one most people reach for.
Everything in a session lands in one growing pile of text: your questions, Claude’s answers, the contents of every file it opened, the output of every command it ran, and your instruction sitting somewhere in there with all of it. The model reads that whole pile before each reply and weighs it together. Your sentence is one voice among many, and it is competing with material that arrived later and is about the thing you are working on right now.
That pile has a name. It is the context window: the fixed amount of text the model can hold at once. Your instruction does not sit outside it giving orders. It sits inside it being read.
So the longer a session runs, the more there is for your sentence to compete with. That is the part most people get wrong, and it is measurable.
Someone measured which part matters
A controlled study tested the thing everyone believes. Damon McMillan’s “Instruction Adherence in Coding Agent Configuration Files: A Factorial Study of Four File-Structure Variables” (submitted 2026-05-11, arxiv.org/abs/2605.10039) ran 1,650 Claude Code sessions and 16,050 function-level observations across two TypeScript codebases, three frontier models and five tasks. It varied four things about how the instruction file was written: its size, where in the file the instruction sat, how the files were split up, and whether a neighbouring file contradicted it.
The result, from the abstract: “None of the four structural variables or three two-way interactions produces a detectable contrast after multiple-testing correction.” Size and contradiction came back with evidence of no effect rather than a failure to find one.
One thing did move the number. Position within the session. Each additional function the agent generated was associated with roughly 5.6% lower odds of compliance per step.
This contradicts the advice you will read everywhere, including Anthropic’s own best-practices page, which says “Bloated CLAUDE.md files cause Claude to ignore your actual instructions!” and tells you to prune. Both can be worth doing. Pruning saves you tokens, which costs real money and real room in the window. What the study says is that pruning is unlikely to be what buys you obedience. Two honest limits on it: it measured compliance with one small target annotation, so it does not license “length never matters”, and it is one study.
Take the useful half. When an instruction keeps slipping, reaching for better wording is usually reaching for the wrong lever. Start a fresh session and the same sentence works again.
Some things you want held every time
Guidance is worth writing anyway. It is cheap, and it covers cases you could never have listed in advance. “Prefer small commits” shapes behaviour you would not have known how to describe as a pattern.
But some rules are not the kind you want weighed. Never commit to the main branch. Never write to that credentials file. For those there is a second mechanism, and it is a program rather than a sentence. A program does not read your intent and take it into account. It runs, and it either lets the action through or it does not.
Where does that program live? In a folder named .claude at the top of your project. Claude Code looks for it every time it starts in that directory. The dot at the front of the name is what hides it from a plain ls, which is why you have not seen it yet. Inside it, a file called settings.json holds configuration in JSON, the format with the curly braces and the quoted keys.
One more thing to know before the file makes sense. Every program that runs hands the system back a single number when it finishes. Zero means it finished cleanly, anything else means it did not. That number is called its exit code, and Claude Code reads it. For most of the moments where your program can run, exit code 2 is the number that stops the action from happening. Exit code 1 does not stop anything: Claude Code treats it as your program having a problem, and carries on with what it was doing.
Here are the two mechanisms side by side, expressing the same intent.
.claude/settings.json Guarantee. A program runs before the command does, and exit code 2 stops it.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "./scripts/no-main-commit.sh" }
]
}
]
}
} Read more
For most hook events, exit code 2 is the only exit code that blocks through the code alone. Exit 1 without valid JSON printed to stdout is treated as a non-blocking error and Claude proceeds anyway. Exit 0 with no output means the hook has no decision to report, so the call continues through the normal permission flow: the hook can deny the call, but staying silent does not approve it. Module 9 builds one of these.
CLAUDE.md Guidance. Loaded into context at session start; the model weighs it against everything else.
# Project conventions
Do not commit directly to main. Branch first. Read more
This is the whole file. CLAUDE.md is plain markdown with no required structure. It gets added to the context window at the start of the session. Nothing checks it, so nothing can reject it either. Module 3 covers which instruction files load and in what order.
The first file persuades. The second one decides. Read the second one again and count how much more you had to say: which moment, which tool, which script. That specificity is the price of the guarantee, and it is why you would not write one of these for “prefer small commits”.
That second mechanism has a name. It is a hook: a program Claude Code runs at a fixed point in its own loop, named in settings.json. Module 9 is where you write one and watch it fire.
Writing exit 1 in the script, because 1 is what a failing program conventionally returns everywhere else. What you see when you do this: nothing. No error, no warning, and the message you printed may well show up somewhere in the transcript, which reads like the block worked. Then the commit lands on main anyway. The only way to catch it is to check whether the command actually ran, rather than trusting that your message appeared. The hooks reference states it flatly: without valid JSON on stdout, exit code 1 is a non-blocking error and Claude Code proceeds with the action.
A hook is not a security wall. It is a program you asked to run, and the person running Claude Code can remove it. What it buys you is that the check happens every time rather than most of the time. Module 11 covers the permission rules, which are the part that holds even when the hook says yes.
The distinction does, because it is a fact about language models rather than about Claude Code. Anything you write as text gets weighed by the model in every tool of this kind. The specific files travel further than you would expect: portability inverted over the past year, and other tools converged on Claude Code’s layout rather than on a neutral standard, so Copilot CLI reads .claude/skills/, .claude/agents/, .claude/commands/, CLAUDE.md, and Claude-format hook JSON without modification. Each module from here ends with a note on where its own artifact lands.
Check yourself
- You cut your
CLAUDE.mdfrom 400 lines to 40 and the instruction still stops holding an hour into a long session. What does the study say that edit was likely to buy you, and what would you change instead? - “Prefer small commits” and “never write to
.env.production” both go intoCLAUDE.md. One of them is in the wrong file. Which one, and what tells you? - You write the same check twice, once as a
PreToolUsehook and once as aPostToolUsehook, and both exit with code 2. Which one keeps the commit off main?
Everything ahead of you is a place to put one or the other. Instruction files, commands, skills, settings, output styles, subagents, hooks, external tool servers, permissions: each module takes one surface, shows you what it holds, and tells you whether what you put there is guidance the model weighs or a guarantee that runs.