Max Narditdata & ai systems
← Articles
10 mindev

CLAUDE.md best practices: what to put in it, what to leave out, and how to measure its size

Keep the rules an agent could never discover by itself, cut whatever it can derive or already receives, and size the file with the meter Claude Code uses. On the two files I checked, my own estimate read a third low. Every surviving rule is a bet that something breaks without it, and some of those bets deserve checking.

Contents

Anthropic’s documentation gives you one number for a CLAUDE.md: “target under 200 lines per CLAUDE.md file.” It also gives you a good test for each line and a table of what to include and exclude. All of that is right, and none of it tells you what the file costs, because a line is not a unit of cost. The model pays in tokens.

I keep two layers of these files. A global one in ~/.claude/CLAUDE.md that every one of my agents loads, and a project one, ./CLAUDE.md or ./.claude/CLAUDE.md, per repository. Claude Code doesn’t let one override the other: it concatenates them, user file first, project file after. I have been cutting, growing and measuring them for months, and the history is in git. What it shows, and the docs leave out, is that each line in a CLAUDE.md is a claim that the agent would go wrong without it, and the claims are cheap to make and rarely checked.

Every docs claim below was checked against Claude Code 2.1.310.

What should you put in a CLAUDE.md?

Lines whose absence the agent cannot notice. A build command Claude can’t guess, a gotcha that fails silently, a convention that differs from the default: if the line is missing, nothing in the session tells the agent to go and look for it.

Anthropic’s best-practices page puts the test as a question to ask of every line:

"Would removing this cause Claude to make mistakes?" If not, cut it.

Its include column is the practical version: Bash commands Claude can’t guess, code style rules that differ from defaults, testing instructions, repository etiquette, architectural decisions specific to the project, environment quirks such as required variables, and “common gotchas or behaviors that aren’t self-evident.” The memory docs add a trigger for when to write a line at all: Claude makes the same mistake a second time, or you type the same correction you typed last session.

When I cut my global guide in July, from 12,866 estimated tokens to 5,694 and then to 3,737, the keep criterion I wrote into the commit was a sharper form of the same test. A line stayed always-on if it was “a rule whose failure gives the agent no signal to go look anything up.” Everything else moved behind a read: the task tool tutorial went to its --help, the monitoring examples to their own doc, the service tables to the runbook. The agent still finds them, because once it is working on a task it knows it needs the tutorial. Nobody goes looking for a rule they don’t know exists, and only that kind of line has to be loaded every time.

What should you leave out of a CLAUDE.md?

Anything Claude can derive from the code, anything Claude Code already injects, procedures that only matter sometimes, and the history of how a rule got there.

The derivable part is in the docs’ exclude column: what Claude can figure out by reading code, standard language conventions, file-by-file descriptions, “information that changes frequently.” The /doctor checkup now proposes exactly these cuts for a checked-in CLAUDE.md. It proposes removing directory layouts, dependency lists and architecture overviews, and keeps “pitfalls, rationale, and conventions that differ from tool defaults.”

The injected part is easy to miss, because you never wrote it. Claude Code already puts things in the context window: the skill list with descriptions, MCP tool names, auto memory and its own instructions for using it. Plugins can add more through hooks. In July I found my global guide re-listing the whole skill roster that the harness injects every session anyway, and a plugin hook adding roughly a thousand tokens I had not accounted for. Duplicating the harness means paying for the same context twice. Before you add a line, look at what is already in the session.

Procedures go to skills. A skill’s description is in context every session, but its body loads only “when you invoke them or when Claude determines they’re relevant.” Instructions for one part of the codebase go to path-scoped rules, and the scoping is the frontmatter, not the folder: a file in .claude/rules/ without a paths field loads at launch like the CLAUDE.md itself. With one, it loads only when Claude reads or edits a matching file:

markdown
---
paths:
  - "src/api/**/*.ts"
---
 
- API handlers validate input with the shared schema module.

History goes to a changelog or to the commit message. More on that below, because the reason and the history are not the same thing.

How big should a CLAUDE.md be?

Small enough that Claude Code’s own count of its tokens stays inside a budget you set on purpose. The 200-line target is a reasonable start, but a line count can pass a file that is expensive.

The docs’ reasoning is right: longer files “consume more context and reduce adherence.” The metric is the weak part. In July one project’s CLAUDE.md passed my audit script’s line check at 83 lines. By the same script’s estimate it was about 12,000 tokens, because most of it sat on a single line of 25,782 characters, and as the next section shows, that estimate runs low. A line limit can’t see any of this, so I switched the script to a token budget per file: 2,500 for most project files, a separate, larger budget for the global guide, and a cap on line length to catch inline dumps.

Imports and comments pull in opposite directions. Imports don’t make a file cheaper: the docs say plainly that @path imports “help you organize a long file but don’t reduce its context cost,” because the imported file loads with the one that imports it. Block-level HTML comments do: they are stripped before the content reaches the context, so a note for human maintainers costs nothing. Comments inside code blocks are kept.

How do you measure a CLAUDE.md’s size in tokens?

Ask Claude Code. The /context command lists every memory file that loaded at launch with a token count, and it works in a headless run too.

Interactively, run /context in the project directory and look under Memory files. From the shell, in the same directory:

shell
claude -p "/context" | awk '/^### Memory Files/{f=1} /^### Skills/{f=0} f'

On my machine that prints a table with one row per file: the user CLAUDE.md, the user rules, the project CLAUDE.md, each with its tokens. Run claude -p "/context" without the filter if your output has a different section after it.

The reason to bother is the gap between that count and a home-made estimate. My script estimates tokens from characters: four Latin characters per token, fewer for Cyrillic. On my global guide (424 lines, 19,114 characters) it says about 4,800 tokens; /context says 7.1k. On a project CLAUDE.md the script put at 2,484, just under my 2,500 budget, /context says 4.1k. Those are two files and rounded readings, and /context can itself fall back to a local estimate, so treat it as the tool’s meter rather than ground truth. Still, on both files my estimate read low by a factor of 1.5 to 1.7. Markdown full of backticks, paths and flags is not prose, and a flat characters-per-token ratio doesn’t know that.

So if you set a budget, pick its unit and its meter together, and use the meter the tool uses. A budget checked with wc -m divided by four is a budget on a different file.

One size limit is hard rather than advisory. A CLAUDE.md or rule file that loads on demand, in a subdirectory or through a paths: match, is cut at a per-file limit: 40,000 characters in a 200K context window; in a 1M window, 200,000 on Opus 4.6 and Sonnet 4.6 and 150,000 on later models. Claude gets the start of the file and a note to read the rest, and until it does, the later instructions aren’t there. Check with wc -m (this applies from 2.1.309 on).

Should you start a CLAUDE.md with /init?

As a starting draft, yes. The docs recommend it, and it beats a blank file. Just read what it produces as the part of a CLAUDE.md that will need cutting first.

/init analyzes the codebase and writes build commands, test instructions and the conventions it discovers. If a CLAUDE.md already exists it suggests improvements instead of overwriting. With CLAUDE_CODE_NEW_INIT=1 set it runs an interactive flow that also offers skills and hooks and shows a proposal before writing anything. It also folds in rules from other tools it finds, such as .cursorrules or Copilot instructions.

A file generated by reading the code is made mostly of what can be derived from the code, which is the first thing /doctor will propose cutting. Keep the commands Claude couldn’t have guessed, delete the layout tour, and then let the file grow the way the docs describe: one line each time Claude makes the same mistake twice. Commit it, so the history of each line lives somewhere other than the file.

Should a CLAUDE.md rule say why it exists?

Yes, in the present tense, next to the rule. The incident that produced it belongs in the commit that added the line. And treat the reason as a hypothesis too, because it can be wrong.

It comes down to how the model reads the file. A bare rule gets weighed against everything else in context with nothing to weigh it by. A rule with its reason can be applied to a situation the author didn’t foresee. I wrote up how this squares with the /doctor prompt audit, which flags history in instruction files as cruft: the reason stays in the file, the war story goes to git, where git blame finds it and no session reads it by accident.

My own guide shows why the reason needs checking as well. In the July cut, one countable rule disappeared: if a reply has more than five bullets, two emojis, one table or three headers, rewrite it as prose. Five days later I put it back, with a dated note saying it had been dropped “the same morning output volume jumped,” and that Opus 5’s longer default replies made the qualitative version of the rule not enough on its own. That was a reasonable inference, and it was not a measurement. The same week, Opus 5 had become the default model, and on the day I restored the rule I also pinned my sessions back to Opus 4.8.

A week later came the measurement I had set up, with the signature fixed in advance: if the formatting rule does its job, bullet and header counts fall; if the model was the cause, length falls while the structure stays. The test subject was the morning brief my agents write. Mean brief length went from 1,932 characters before the cut to 2,935 after it, then back to 1,775 over the seven briefs after the pin. Bullets per brief went 12.4, 14.8, 19.0. The counts the rule targets never fell, while the length came back down once the sessions ran on Opus 4.8 again. Both changes landed together, so this can’t isolate the rule’s effect, and brief length also follows how much happened overnight. The model is the best-supported cause, not a proven one.

The rule is still in the guide, and its note still argues that the rule is what holds the model’s length in check, which the measurement didn’t confirm. That is the failure mode to watch for in your own file: a reason written at the moment of the fix reads as settled forever after. If a line matters, write down what would show it working, and look once.

Does IMPORTANT work in a CLAUDE.md?

Used on a single line, emphasis can help; spread across many lines, it stops meaning anything. And no amount of it turns a CLAUDE.md line into a guarantee.

The best-practices page says it directly:

If Claude keeps skipping one instruction, add emphasis such as
"IMPORTANT" to that line alone. If you emphasize many lines, none of
them stands out.

That fits with the prompt audit’s case against pressure language: a file full of capitals reads as a file of alarms, and current models over-apply them. One emphasized line with its reason beside it is the version that survives both.

Emphasis also has a ceiling. The docs say CLAUDE.md content arrives as a user message, after Claude Code’s own instructions rather than inside them, and Claude “tries to follow it, but there’s no guarantee of strict compliance.” The docs’ own advice for anything that must happen every time is a hook, which runs as a shell command at a fixed point whatever the model decides. A rule that has to hold belongs in a gate, not in prose, and once it’s in the gate the CLAUDE.md line can shrink to a pointer, or go.

How do you keep a CLAUDE.md from growing back?

Write down its size each time you touch it, with the tool’s meter, and expect it to grow anyway. Growth comes one reasonable line at a time, and a budget check alone won’t flag it until it is already large.

My global guide is the example. After the July cut it was 3,737 tokens by my script, under a budget of 5,000 that I set for it that same day. Three months later the same script says 4,789: 28 percent more, every addition justified when it went in. The automated check still passes, because the file is under budget and the script allows 5 percent on top before it warns. What shows the growth is the two recorded numbers side by side. And by /context the file is already at 7.1k, well past the budget the script says it meets.

The checks that keep drift visible each catch something different. Running /context after editing a file every session loads gives you the number to compare with last time. /doctor on a checked-in CLAUDE.md finds lines that have become derivable from the code. /doctor prompt-audit after a model change finds rules written for the model you left, contradictions between files and paths that no longer exist. Claude Code also warns at startup and in /status when one instruction file is over the recommended length, or when several small ones add up past a combined limit. If your repository carries an AGENTS.md too, check which file actually loads: by default Claude Code reads AGENTS.md only as a fallback, when there is no CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md in the working directory or above it. Your ~/.claude/CLAUDE.md doesn’t count for that check, and /context shows which one you got.

None of these checks will tell you whether a line still earns its place. They tell you the file changed. Whether the reasons in it are still true is the one check you have to run by hand.

01 / Discussion

No comment section here — all discussions happen on X.

Max Nardit@mnardit

02 / More articles