Your CLAUDE.md Describes Your Folders. The Agent Already Knows Them.
I run Claude Code on this site's Symfony codebase most days. Its CLAUDE.md is 74 lines I wrote by hand. Next to it sits a memory directory with eight short notes Claude wrote itself after I corrected it — one exists because I told it, in a single sentence, that I check rendered pages in my own browser and it should stop curling the dev server. I can't recall that note being ignored once. Rules from the hand-written file, I can: they hold at the start of a session and get patchier the longer it runs.
For a while I drew the conclusion that is fashionable right now: instruction files are overrated, the agent can work the project out by itself, and the only thing worth writing is a short map — src/Seo/ holds this, src/AiTools/ holds that. Then I read the measurements on that last part. The map turned out to be the one section that measurably does nothing.
The grep test: if the agent can find it, don't write it down
A line in CLAUDE.md earns its place only if the agent cannot recover the same fact with a couple of ls, grep or file reads. Coding agents explore anyway. Anthropic describes Claude Code as a hybrid: instruction files are dropped into context up front, while glob and grep pull in files just in time. Whatever you pre-load competes with what the agent is about to discover on its own — and what it discovers is current, while your prose was current on the day you wrote it.
This is the same trade I described in context engineering as a patch with an expiry date: hand-curated context made sense when models couldn't search. They can now, so the question shifts from "what should the agent know?" to "what can't it find?"
Run the test on real lines from this project and the file sorts itself:
| Fact | Can the agent find it? | Where it goes |
|---|---|---|
Directory layout, what src/Seo/ contains |
Yes — ls and one read |
Nowhere |
| Framework and dependencies | Yes — composer.json |
Nowhere |
"PHP 8.5, prefer array_find()" |
The version yes, the preference no | CLAUDE.md, one line |
| "SeoItem is meta only; related tools come from the controller" | No — the code shows the outcome, not the decision | CLAUDE.md |
| "Don't curl the dev server, I check pages myself" | No — it's a personal preference | Memory |
| The 118-line article-writing workflow | No, but only one kind of task needs it | Skill |
| LF line endings | Enforceable by git | .gitattributes, not prose |
Repository overviews are the part that measurably doesn't work
The only controlled measurement of repository overviews in context files found that they don't shorten the agent's path to the right code. In the ETH Zurich AGENTS.md study, every context file generated by Sonnet 4.5 contained a codebase overview. The researchers then counted how many steps agents needed to reach the files relevant to each fix, and the number did not meaningfully drop — on SWE-bench Lite or on 138 tasks from repositories with developer-written files. Their words: context files, "even developer-provided ones, are not effective at providing a repository overview."
The vendor agrees. Claude Code's /doctor check proposes trims for a checked-in CLAUDE.md, and the documentation says what it cuts: "directory layouts, dependency lists, and architecture overviews". It keeps "pitfalls, rationale, and conventions that differ from tool defaults". Auto memory follows the same rule and skips "anything it can derive from the codebase, such as architecture, file paths".
I had this backwards. My instinct was that a layout map is the harmless minimum. It isn't harmless: directories get renamed, the map doesn't, and an agent trusting a stale map is worse off than an agent with no map. A layout line survives only when it is really a decision in disguise. "src/Seo/ contains SEO classes" is grep-able noise. "SeoItem is for meta and schema only — content data goes in the controller" stops a mistake the agent made on this codebase, and nothing in the directory listing would have prevented it.
What the agent cannot grep: new APIs, decisions and traps
Models are good at discovering structure and bad at knowing what is neither in their training data nor in your code. Vercel measured the first half in January 2026 with evals on Next.js 16 APIs the models had never seen — 'use cache', cacheLife(), forbidden(), async cookies():
| Configuration | Pass rate |
|---|---|
| No docs | 53% |
| Skill with the docs, default | 53% |
| Skill with explicit instructions to use it | 79% |
8KB docs index in AGENTS.md |
100% |
The winning AGENTS.md did not contain the documentation. It was a compressed index, cut from 40KB to 8KB, that mapped topics to files in a .next-docs/ directory the agent could open. A pointer to knowledge the model lacks, not a pile of knowledge it already has.
Beyond post-cutoff APIs, three kinds of fact fail the grep test. Commands that differ from the obvious ones. Decisions whose reason isn't visible in the code. Traps — the things that break quietly. Here is roughly what that looks like for this site, every line taken from a real correction:
## Stack
PHP 8.5, Symfony 8.1 - prefer `array_find()`, typed constants, `readonly class`
## Decisions
- SeoItem is meta/schema only; related tools are passed from the controller
- Parsers only parse; "is it published?" needs the clock and lives in ArticleRenderer
## Traps
- Route names contain digits (`app_encoder_base64`) - grep with `app_[a-z0-9_]+`
- An article with a future `publishedAt` 404s and links to it render as plain text
## Verification
Skip for templates, translations and copy. Run for new routing or DI wiring.
No directory map, no "write clean code", no framework tutorial. The digit trap alone cost me a broken audit script that silently truncated route names — exactly the kind of fact no amount of exploration surfaces until it has already bitten.
Why instruction files fade in long sessions
Instruction adherence drops as context grows, and a long agent session is mostly tool output piling up on top of the instructions loaded at the start. Chroma's 2025 context rot study tested 18 models, including GPT-4.1, Claude Opus 4 and Gemini 2.5, and every one got worse as input length grew — well before the window was full. The IFScale benchmark found the same shape for instruction count: the dominant failure is silent omission, and rules near the start are favoured over the rest.
To be fair to the models, that ceiling has moved. Arize reran IFScale in 2026 and found the point where frontier models start losing constraints had jumped from a few hundred to thousands — GPT 5.5 held 99% at 5,000. But IFScale counts whether required keywords appear in a report. "Never add PHPDoc blocks" at hour three of a refactor is a harder test, and nobody publishes that benchmark.
Before blaming the model, check three Claude Code specifics:
CLAUDE.mdis delivered as a user message after the system prompt, not as part of it. The documentation says so plainly and adds that there is "no guarantee of strict compliance".- The recommended ceiling is 200 lines per file, because "longer files consume more context and reduce adherence" — the token cost of bloated custom instructions is only half the bill.
- The project-root
CLAUDE.mdis re-read from disk after/compact. If a rule vanished after compaction, it was most likely only ever said in chat.
Why memory notes hold better than hand-written rules
Memory is not loaded by a more reliable mechanism — it is more reliable because of what ends up in it. The documentation is explicit that CLAUDE.md and auto memory are both loaded at the start of every conversation, and both are treated "as context, not enforced configuration". So when my memory notes outlast my CLAUDE.md rules, the difference has to be in the content.
Every memory note answers a real failure. Claude writes one after I correct it, so each note targets a mistake the model demonstrably makes on this codebase. Most of a hand-written file targets mistakes I imagined in advance, and some of those the model would never have made.
The always-on part is also tiny. Only MEMORY.md loads at startup — one line per note, capped at the first 200 lines or 25KB — and the note files themselves are read on demand. That is progressive disclosure: a short index in context, detail one file read away. And the notes carry their reason. My note against {{ var|json_encode|raw }} in Twig includes a Why: line explaining that it defeats auto-escaping for no gain on trusted scalars, which gives the model a principle to generalise instead of a string to match.
Memory has its own failure mode, and my directory held a textbook case until I wrote this article. dev_server.md said the server was running and to "just use curl directly". A feedback note written weeks later said never to curl it. When two rules contradict, the documentation warns that Claude "may pick one arbitrarily" — so audit /memory the way you review code. Also remember that auto memory is machine-local: a rule your teammates need still belongs in the committed file.
Skills hold procedures, hooks hold guarantees
Anything that is a procedure rather than a fact belongs in a skill, and anything that must happen every single time belongs in a hook. A skill's description sits in context; its body loads only when the skill is invoked. The article workflow on this site is a 118-line SKILL.md plus three reference files and an audit script. In CLAUDE.md it would be paid for in every session, including the ones where I fix a Twig template.
The catch is invocation. In Vercel's eval the agent never invoked the skill in 56% of cases, and explicit "use the skill" instructions made results swing on small wording changes. Skills suit actions you trigger by name, like "write an article" or a deploy; they are a poor home for knowledge the agent must apply without being asked. I measured what separates the agent skills that pay off from the ones that sit unused in a separate piece.
Hooks are the only mechanism that doesn't depend on the model deciding anything. My own CLAUDE.md says "Always LF" — and this repository has no .gitattributes, so the rule is enforced by nothing but a model's attention span. One line fixes that permanently: * text=auto eol=lf. Formatting works the same way with a Claude Code hook, if you run PHP-CS-Fixer:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | grep '\\.php$' | xargs -r vendor/bin/php-cs-fixer fix --quiet"
}
]
}
]
}
}
Put together, every piece of project knowledge has exactly one right home:
| Mechanism | In context | Written by | Put here | Breaks when |
|---|---|---|---|---|
CLAUDE.md |
Every session, re-read after /compact |
You | Versions, decisions, traps, non-obvious commands | It restates the code or passes 200 lines |
| Auto memory | Index every session, notes on demand | Claude, after corrections | Preferences, corrections, context outside the repo | Notes contradict each other |
| Skill | Description always, body on invocation | You | Multi-step procedures, large references | The agent never decides to invoke it |
| Hook | Never — runs at lifecycle events | You | Anything that must happen every time | The command is slow or flaky |
| Nothing | — | — | Layout, dependencies, architecture overview | — |
Run the Grep Test on Your Own File
Open your CLAUDE.md and put every line through four questions, in this order. Can the agent find it with ls, grep or one file read? Delete it. Must it happen every time, whatever the model decides? Make it a hook or a git setting. Is it a procedure only some tasks need? Move it into a skill. Is it a preference you'd only mention after being annoyed? Leave it out and let memory catch it the first time it matters.
What survives is your file — usually a stack line, a handful of decisions and the traps that have already bitten. If you want a lean skeleton to cut down from rather than a blank page, the Custom Instructions templates for Claude Code, Cursor and Codex start small on purpose. Then run a week of real work and add a line only when the agent gets something wrong twice.