A CLAUDE.md is a prompt, not a config file
Claude Code reads CLAUDE.md into the context window at the start of every session. Anthropic's own documentation is blunt about what that means: the file is context, not enforced configuration. If you need an action blocked no matter what the model decides, the docs point you at a PreToolUse hook instead.
That single sentence explains almost every disappointing CLAUDE.md. People write them the way they write an .eslintrc, as a set of switches, and then wonder why half the switches do nothing. There are no switches. There is a paragraph of text stapled to the front of every conversation, and it competes for attention with everything else in the window.
So the question to ask of every line is not "is this rule correct?" but "would a literal-minded new hire know what to do differently after reading this?" That is a writing problem, and it has known answers: name the role, name the task, give the constraints in checkable form, show an example. The same parts the analyser here scores are the parts an instruction file is usually missing.
Vague rule, specific rule
The docs give three examples of the swap, and they are worth reading as a pattern rather than as three tips. In each pair, the second version can be checked by looking at a diff. The first cannot.
| Does nothing | Changes the diff |
|---|---|
| Format code properly | Use 2-space indentation |
| Test your changes | Run npm test before committing |
| Keep files organized | API handlers live in src/api/handlers/ |
| Write clean, maintainable code | Functions under 50 lines; extract rather than nest past 3 levels |
| Follow our conventions | Components are named exports; no default export in src/components/ |
| Handle errors properly | Every catch either rethrows or logs with the request id. Never an empty block |
The test is mechanical. Read the rule, then ask what you would point at in a pull request to prove it was broken. If you cannot answer, the model cannot either, and the line is costing you tokens for nothing.
Say where things are, not that things should be somewhere
The highest-value lines in a real CLAUDE.md are usually not rules at all. They are facts about the repository that take an agent four tool calls to discover:
## Layout
- Route handlers: `src/app/api/**/route.ts`
- Mongo models: `src/lib/models/`. One file per collection.
- Self-checks: `scripts/check-*.ts`, run with `npx tsx`. No test framework.
## Commands
- `npm run dev` on port 3000. Do not run `npm run build` while dev is up;
it overwrites `.next` and the dev server then serves unstyled HTML.
- `npx tsx scripts/check-templates.ts` must pass before any commit that
touches `src/lib/templates.ts`.Note the second command. It records a trap somebody actually fell into. A CLAUDE.md earns its length fastest by holding the things that are true about this repository and discoverable nowhere else.
Two hundred lines is the budget
The docs name a target: under 200 lines per CLAUDE.md. The reason given is not disk space. Longer files consume more context and reduce adherence. The file competes with itself. Every line you add makes the other lines slightly less likely to be followed.
This is the opposite of how most teams treat the file. It accretes. Someone has a bad session, adds four lines, and nobody ever deletes any. Six months later it is 600 lines and the model follows roughly none of it, which gets read as the model being unreliable rather than the file being unreadable.
Two habits keep it under budget. Delete a rule the moment the code makes it impossible to break, because a linter or a type already enforces it and the sentence is now redundant. And move anything that only matters in one corner of the repo out of CLAUDE.md entirely.
What to move out, and where to
Claude Code has three other places for instructions, and using them is how the main file stays short:
.claude/rules/*.mdfor topic files. One file per subject, descriptive names liketesting.mdorapi-design.md, discovered recursively so you can nest them underfrontend/andbackend/. Frontmatter can scope a rule to matching paths, so it loads only when Claude touches those files.- A subdirectory
CLAUDE.mdfor instructions that are only true inside that directory. These are not loaded at launch. They come in when Claude reads a file in that directory, which is exactly when you want them. - A hook for anything that must not happen.
PreToolUseis enforcement;CLAUDE.mdis persuasion. If the consequence of the model getting it wrong is a dropped table or a force push, do not write a sentence about it.
There is also @path/to/file import syntax, which expands the referenced file into context at launch. It is good for organisation and does nothing for your context budget: an imported file costs exactly what the same text pasted inline would cost. Imports resolve relative to the file containing them, and recurse up to four hops deep.
One genuinely free trick: block-level HTML comments are stripped before the content reaches Claude. <!-- superseded by the rules/ split, delete after Q4 --> is a note to your teammates that costs zero tokens.
Where the file goes
Four scopes, loaded broadest first, so the most specific file is read last and wins on any disagreement:
| Path | Scope |
|---|---|
/Library/Application Support/ClaudeCode/CLAUDE.md (macOS), /etc/claude-code/CLAUDE.md (Linux, WSL) | Managed policy, set by IT for everyone in the organisation |
~/.claude/CLAUDE.md | You, in every project |
./CLAUDE.md or ./.claude/CLAUDE.md | The team, committed to source control |
./CLAUDE.local.md | You, in this project only. Gitignore it |
Files in directories above your working directory load at launch too, so a monorepo root file applies in every package. claudeMdExcludes exists for the case where that means inheriting four other teams' instructions.
Two commands verify all of this rather than assuming it. /context lists the memory files that loaded, and /memory shows what Claude actually read. If a rule is being ignored, check that its file is on that list before rewriting the rule.
Contradictions are worse than gaps
The docs note that when two rules contradict each other, Claude may pick one arbitrarily. Arbitrarily is the word to sit with. Not "the more specific one", not "the later one". You get one of them, and which one can change between sessions.
This is how a file gets a reputation for being flaky. Line 12 says "prefer small focused commits" and line 140, added by someone else, says "squash everything into one commit before opening a PR". Both are reasonable. Together they are noise, and the noise spreads: a model that has learned this file is internally inconsistent has less reason to treat any of it as binding.
Read the whole file end to end when you add to it. At 200 lines that takes two minutes, which is the other argument for the budget.
A CLAUDE.md worth committing
Short, specific, and every line checkable. /init will generate a first draft by reading your codebase, and it suggests improvements rather than overwriting if a file already exists. Use it for the layout and commands, then add the things it cannot know:
# Payments service
Node 20, TypeScript strict, Postgres via Prisma. Deploys on merge to `main`.
## Layout
- HTTP handlers: `src/routes/`. One file per resource, no logic in them.
- Business logic: `src/domain/`. Pure functions, no Prisma imports.
- Prisma access: `src/repo/` only. If you need a query, add it here.
## Commands
- `npm run dev` (port 4000), `npm test` (vitest), `npm run migrate:dev`.
- Run `npm test` and `npx tsc --noEmit` before you say a change is done.
## Rules
- Money is `bigint` minor units. Never a float, never a `number`.
- Every route validates its body with a zod schema exported from
`src/schemas/`. No hand-rolled `if (!req.body.x)` checks.
- Errors: throw `AppError` with a `code`. The error middleware maps codes to
status. Do not call `res.status(500)` anywhere.
- Migrations are additive. Add a column, backfill, drop the old one in a
separate migration. Never change a column type in place.
- Do not edit `src/generated/`. It comes from `npm run codegen`.
## Gotchas
- `npm run migrate:dev` resets the local DB. Use `migrate:deploy` if you
need the seed data kept.
- The Stripe webhook test needs `stripe listen` running, or it hangs for 30s
and then fails with a timeout that looks like a code bug.Forty lines. Every rule names a path, a command, a type, or a specific failure. A reader who has never seen the repository could open a pull request against it.
Checking your own file
Being honest about what tooling can do here: nothing scores a CLAUDE.md as a whole, and this site does not claim to. The scorer at the desk measures whether the parts of a task prompt are present, and an instruction file is a different shape of document. Pasting the whole thing in will not give you a meaningful number.
What does transfer is the reading. The rubric asks seven questions of a prompt, and five of them are exactly the questions a weak instruction file fails: is the role named, is the task concrete, are the constraints checkable, is the output shape specified, is there an example. The lessons on constraints, specificity and output contracts are each about five minutes and are the same edit applied to a different document.
The practical version: take the one rule in your file you suspect is being ignored, rewrite it as an instruction you could hand to a contractor, and see whether the next session goes differently. If you want to feel the difference between a vague instruction and a specific one before you commit to rewriting the whole file, paste a weak one into the desk and read what it says is missing.