CLAUDE.md Best Practices: The Complete Guide
What goes in CLAUDE.md, what to cut, where each file lives and what it costs you. Plus the import trap that put 70k tokens into every session of a 13-app build.
CLAUDE.md is not documentation. It is the part of your context window you already spent before you typed a word.
Claude Code starts every session with no memory of your project. CLAUDE.md is how you fix that: a Markdown file Claude Code reads at the start of every session and keeps in context until the session ends. Write it well and Claude stops repeating the same mistakes. Write it badly and you pay for it on every request, in tokens and in instructions Claude half follows.
Most CLAUDE.md advice is a template. This guide is the decisions: what earns a line, what gets cut, which file each instruction belongs in, and how to check what you are actually paying. I learned the expensive part on a 13-app fintech I built in 70 days, where a CLAUDE.md that looked lean was loading almost 70,000 tokens into every session.
CLAUDE.md is context, not configuration
Get this one wrong and nothing else in the file works. Anthropic’s docs say it plainly: Claude “treats them as context, not enforced configuration.” Claude reads your CLAUDE.md the way a new engineer reads the onboarding doc. It usually follows it. It does not always follow it. And the more a file holds, the less of it gets followed.
That splits every instruction into two kinds. The kind that needs judgment (“scope every query by merchant”) belongs in CLAUDE.md, because only a reader can apply it. The kind that must never be broken (“never run kubectl apply”) does not belong in a file Claude might skim. It belongs in a hook or a linter, where the check runs whether Claude remembers the rule or not. More on that below.
The project-root CLAUDE.md also survives compaction. After /compact, Claude Code re-reads it from disk and injects it again. Everything else you said in chat can get summarized away. The file stays.
Where CLAUDE.md lives decides when you pay for it
There is not one CLAUDE.md. There are several, and they load at different moments. Files in your working directory and every folder above it load at launch and stack up, from the filesystem root down to where you started Claude, so the closest one is read last. Nothing overrides anything. Everything is concatenated, and if two files contradict each other, the docs warn that Claude “may pick one arbitrarily.”
Every place Claude Code looks
| File | When it loads | Use it for |
|---|---|---|
| ~/.claude/CLAUDE.md | Every session, every project | Your own habits: tools you use, how you like commits |
| ./CLAUDE.md or ./.claude/CLAUDE.md | Every session in this repo | The team's rules. Committed with the code |
| ./CLAUDE.local.md | Every session in this repo, only for you | Your sandbox URLs and test data. Gitignore it |
| subfolder/CLAUDE.md | When Claude opens a file there with Read, Edit or Write | Rules for one package of a monorepo |
| .claude/rules/*.md | At launch, like the project file | Splitting a big file by topic. Same cost |
| .claude/rules/*.md with paths: | When Claude opens a matching file with Read, Edit or Write | Rules that only matter for some files |
| Managed policy file | Every session, for everyone on the machine | Company-wide rules your IT team sets |
Read the second column again. Anything loaded at launch costs you on every session, whether the task touches it or not. Anything loaded on demand costs you only when the work gets there. Good CLAUDE.md hygiene is mostly moving lines from the first group to the second. One catch: on-demand files load when Claude opens a file with its Read, Edit or Write tools. A grep through Bash does not count, which is the usual reason a rule “did not load.”
The import that looked like a link
On the fintech build, my CLAUDE.md was 124 lines. A project summary, the commands, eleven critical rules, and then a tidy set of tables pointing to twenty topic files: auth roles, database schema, design tokens, UI components, testing conventions, infrastructure. It read like a table of contents.
It was not a table of contents. Every entry was written as @.claude/ui/components.md, and in a CLAUDE.md the @ is an import. The docs are clear about what that means: “Imported files are expanded and loaded into context at launch alongside the CLAUDE.md that references them.” Imports can nest up to four hops deep.
What that 124-line file actually loaded
- 124
- linesin the CLAUDE.md itself
- 20
- importswritten as @path in tables
- ~175 KB
- loadedat the start of every session
- 69.8k
- tokensmeasured with /context
Seventy thousand tokens is over a third of a 200k context window, gone before the first message. The irony is that I had already written the rule against this. My AGENTS.md guide says to point to deep docs, not paste them inline. I followed the rule in spirit and broke it in syntax.
The fix is one character. The docs again: “Import parsing skips Markdown code spans.” Wrap the path in backticks and it stops being an import. It becomes text Claude can choose to open.
@.claude/ui/components.md
- 01An import
- 02Expanded into context at launch
- 03Paid on every session, every task
- 04Nests up to four hops
- 05Always seen, so always diluting
`.claude/ui/components.md`
- 01A pointer
- 02Plain text in the file
- 03Paid only if Claude opens it
- 04Claude decides when it is relevant
- 05Can be missed, so not for must-follow rules
The last row matters. A pointer only works if Claude decides to open it. For a rule that must apply whenever Claude touches certain files, a pointer is too weak and an import is too expensive. That is exactly what path-scoped rules are for.
Put every instruction where it can actually work
Once you stop thinking of CLAUDE.md as the place for everything, the question for each line becomes: what is the cheapest mechanism that still makes this happen? There are six.
Where each kind of instruction belongs
| The instruction is… | Put it in | Example from the fintech |
|---|---|---|
| True everywhere and needs judgment | CLAUDE.md | Never let one merchant's data reach another |
| Only true for some files | .claude/rules/ with paths: | Design tokens, only when a frontend file is open |
| A multi-step procedure | A skill | Create a migration, run it, add the test |
| Must never happen | A hook or a linter | Never run kubectl apply on Terraform-managed infra |
| Only true for you | CLAUDE.local.md or ~/.claude/CLAUDE.md | Your local HTTPS hostnames |
| Something Claude learned from a correction | Auto memory | Let Claude write it. Its index loads up to 200 lines or 25 KB |
Skills deserve one more sentence, because they are the best deal on the list. A skill’s body loads only when you invoke it or when Claude decides it fits the task. A twenty-step deploy procedure in CLAUDE.md costs you every session. The same procedure as a skill costs you on deploy day. On the fintech, eight project commands sat in every session as skills at about 20 tokens each, until someone called one. The skill vs prompt vs memory guide goes deeper on that split.
Capital letters are not enforcement
My file had eleven rules in the shape of “NEVER do X” and “ALWAYS do Y.” Shouting them did not change what they were: context that Claude reads and usually follows. Three of them did not need Claude’s judgment at all. They needed a machine to say no.
“NEVER disable ESLint rules” is one line of ESLint config: linterOptions: { noInlineConfig: true }, and every inline disable comment stops working. “NEVER use arbitrary Tailwind values” is a lint rule too. This site runs exactly that rule, and pnpm verify fails on p-[13px]. And “NEVER run kubectl apply” is a PreToolUse hook, which runs before Claude executes a command and can block it:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' | grep -qE 'kubectl.* apply' && { echo 'Infra is managed by Terraform. Use terraform apply.' >&2; exit 2; } || exit 0"
}
]
}
]
}
}
Exit code 2 blocks the call and hands the message back to Claude, so it learns why and switches to Terraform. Put that in .claude/settings.json and the rule holds on the day Claude skims your file, too. A rule that can be enforced should be enforced. CLAUDE.md is for the rules that can’t.
What earns a line in CLAUDE.md
The docs give the best test I have seen for when to add a line. Add it when Claude makes the same mistake a second time, when code review catches something Claude should have known, when you type the same correction you typed last session, or when a new teammate would need the same context. What the file should not hold is the history around the code: decisions, people, what each meeting settled. That grows without limit, and it belongs in an LLM wiki the agent reads on demand. If none of those happened, the line is a guess.
What belongs in the project CLAUDE.md
- Required:Two or three lines on what the project isThe stack and the shape of the repo. Not the pitch, not the architecture essay.
- Required:The commands Claude should runSetup, dev server, the one command that verifies everything. Claude cannot guess these.
- Required:Conventions a strong engineer would still get wrongWhere API errors get translated. Which package owns auth. Anything non-standard.
- Required:Rules that need judgmentData isolation, transactions, what never leaves the server. Write them so they can be checked.
- Required:Pointers in backticks to deeper docsPaths Claude opens when the task needs them. Not @imports.
- Anti-pattern:Rules a linter or a hook could enforceEnforce them. A file can only ask.
- Anti-pattern:Long references for one part of the codebaseMove them to a path-scoped rule so they load with the files they describe.
- Anti-pattern:Generic advice like 'write clean code'Claude already does this. The line costs tokens and changes nothing.
Write each line so you could check whether Claude followed it. The docs’ own examples are good: “Use 2-space indentation” instead of “Format code properly,” “Run npm test before committing” instead of “Test your changes.” If you cannot tell whether a rule was broken, Claude cannot tell either.
Don’t let /init write it for you
/init reads your codebase and drafts a CLAUDE.md. It is a fine starting point and a bad finished file, and now there is data on why. Evaluating AGENTS.md, a 2026 study of coding agents on real GitHub issues, found that context files did not generally raise success rates and added over 20 percent to inference cost on average. Files generated by an LLM did slightly worse than no file. Files the repo’s own developers wrote did only slightly better. What helped was the non-standard stuff, the conventions a model would not guess. The repository overview, the part /init is best at, did not.
So run /init, then cut. The docs say to refine it “with instructions Claude wouldn’t discover on its own.” Everything Claude could discover by reading the code is a line you are paying to repeat. The AGENTS.md guide has the pruning test I use: if removing a line would not cause a mistake, remove it.
A CLAUDE.md example: the fintech file, rewritten
Here is the 124-line file as I would write it today. Same project, same rules that matter, no imports.
# Payment Platform
PIX payment gateway with on-chain settlement on the Liquid Network.
Turborepo + pnpm. Fastify 5, Drizzle, Zod. Next.js, Tailwind. PostgreSQL, Redis.
## Commands
- `pnpm setup:dev`: Docker, databases, migrations, seed
- `pnpm dev:https`: dev servers behind Caddy
- `pnpm verify`: format, lint, types, tests. Run it before every commit.
## Layout
- `apps/<domain>/api`: Fastify APIs for auth, exchange and payment
- `apps/<domain>/<app>`: Next.js frontends
- `packages/ui`, `packages/i18n`, `packages/auth`: shared code
## Rules that need judgment
- One merchant's data never reaches another merchant. Scope every query by merchant.
- Validate on the server. Never trust the client.
- Writes that touch more than one table run in a transaction.
- APIs return English messages plus a code from `ERROR_CODES`.
Frontends translate with `getErrorMessage(code)`.
## Read when the task needs it
- Local setup: `.claude/setup/local-development.md`
- Git workflow: `.claude/git/workflow.md`
- Infrastructure: `infrastructure/README.md`
Under 40 lines. The rest of the twenty files did not disappear. They move to where they load with the work that needs them.
Where the other docs would go
- .claude/
- CLAUDE.mdalways// the file above
- settings.jsonenforced// the kubectl hook
- rules/
- frontend.mdpaths// frontends and packages/ui: tokens, components, UX
- database.mdpaths// apps/*/api/src/db/**: schema, migrations, entities
- auth.mdpaths// APIs and packages/auth: roles, scopes, route permissions
- testing.mdpaths// test and e2e folders: test conventions
- pricing.mdpaths// apps/exchange/**: the pricing engine
- skills/
- new-migration/on demand// the procedure, loaded when used
A path-scoped rule is a normal Markdown file with one extra field. This one only loads when Claude reads or edits a file under a database folder:
---
paths:
- "apps/*/api/src/db/**"
---
# Database
- Every table: a prefixed ID (`mer_`, `cus_`, `ord_`), `createdAt`, `updatedAt`,
and an index on every foreign key.
- Tables are snake_case plural. Columns are snake_case.
- Relations live in a separate `relations.ts`.
The frontend rule is still big. The UI component reference alone is 10.8k tokens. But now it loads when Claude opens a React component, not when it fixes a webhook retry. That is the trade: the cost did not vanish, it moved to the moment it buys something.
Measure what you load, don’t guess
My first guess for the fintech, from file sizes, was 45,000 tokens. /context on the old repo said 69,800. The guess was off by a third, in the direction that flatters you. Measure it. Claude Code ships the tools.
Inside a Claude Code session
See every memory file that loaded and how many tokens it takes
/contextOpen and edit the memory files Claude Code found
/memoryFind stale, missing or contradicting instructions (v2.1.283+)
/doctor prompt-audit
Run /context on your main repo today. If the memory files take more than a few thousand tokens, open the biggest one and ask, line by line, which session actually needed it.
CLAUDE.md vs AGENTS.md: one file is enough
If your repo is touched by more than one agent, you may not need a CLAUDE.md at all. Since v2.1.277, Claude Code reads AGENTS.md as project instructions when there is no CLAUDE.md or CLAUDE.local.md in your folder or above it, and Codex, Cursor, Copilot and others read it too. Note the second file: adding a CLAUDE.local.md quietly stops Claude from reading your AGENTS.md. If both kinds of file exist, Claude Code reads only the CLAUDE.md files by default, unless you set Project instructions to claude-md-and-agents-md in /config.
The clean setup is one shared AGENTS.md, plus a CLAUDE.md that starts with @AGENTS.md only when Claude needs lines the other agents should not see. That one import is fine, because you want the whole file every session. The AGENTS.md guide has the full table of which agent reads what.
Keep it true or delete it
A wrong CLAUDE.md is worse than none, because Claude follows it with confidence. Change the file in the same pull request that changes the convention. Move the database from Prisma to Drizzle and the line about Prisma dies in that commit, not three months later when Claude writes a Prisma query.
Every few weeks, run /context, look at the number, and cut. The best CLAUDE.md I have is the one for this site: a short AGENTS.md at the root and a handful of rules in .claude/rules/ that load only for the files they describe. /context puts its project memory at 2,000 tokens. The fintech’s was 69,800. It is not complete. It is cheap, and it is true.
CLAUDE.md, quick answers
Where does CLAUDE.md go?
For a project, in the repo root as CLAUDE.md or in .claude/CLAUDE.md, committed with the code. For rules that apply to all your projects, in ~/.claude/CLAUDE.md. For personal notes about one project, in CLAUDE.local.md in the repo root, added to .gitignore. A CLAUDE.md inside a subfolder loads only when Claude works on files in that folder.
How long should CLAUDE.md be?
Anthropic's docs say to target under 200 lines per file, because longer files take more context and reduce adherence.
Count the imports too. A 124-line file with twenty @imports is not a 124-line file. Run /context to see the real size.
Does Claude read CLAUDE.md every time?
Yes, at the start of every session, and it stays in context for the whole session. After /compact, Claude Code re-reads the project-root CLAUDE.md from disk. CLAUDE.md files in subfolders and path-scoped rules load only when Claude reads or edits a matching file.
Can CLAUDE.md import other files?
Yes, with @path/to/file. Imported files load at launch, so they cost the same as pasting them in, and they can nest up to four hops. To mention a path without importing it, put it in backticks.
Why is Claude ignoring my CLAUDE.md?
First check that it loaded: /context lists every memory file loaded at launch. Then check its size and whether two files contradict each other, because Claude may pick one arbitrarily.
If a rule must never be broken, stop asking in a file. Use a PreToolUse hook or a linter that blocks it.
How do I use CLAUDE.md in a monorepo?
Keep the rules every package shares in the root file. Put each package's rules in a CLAUDE.md inside its folder or in a path-scoped rule, so they load only when Claude works there. If other teams' files load in your sessions, skip them with claudeMdExcludes in .claude/settings.local.json.
What is the difference between CLAUDE.md and CLAUDE.local.md?
CLAUDE.md is for the team and goes into version control. CLAUDE.local.md is for you, sits next to it, and goes into .gitignore. Both load at launch, and the local file is added after the shared one.
Should I commit CLAUDE.md?
Commit the project CLAUDE.md. It describes how to work in the codebase and should change in the same pull requests that change the codebase. Keep anything personal in CLAUDE.local.md and anything secret out of both.
The newsletter
Don’t Code, Specify. A weekly dispatch from where AI agents meet real production. No hype, just what shipped and what broke.
Subscribe on Substack (opens in a new tab)