CLAUDE.md Example: A Structure That Actually Helps
In short
A good CLAUDE.md is short, specific, and made entirely of things Claude cannot work out on its own. Anthropic's own guidance is to keep each file under 200 lines, because longer files consume context and reduce adherence. The structure that survives a long project is five blocks: a one-line project description, the commands you actually run, where things live, the conventions that differ from tool defaults, and the traps that have already burned someone. Everything else belongs somewhere better — path-scoped rules in .claude/rules/, on-demand skills, or hooks when you need enforcement rather than suggestion. CLAUDE.md is delivered as context, not as configuration, so there is no guarantee Claude follows it. Write instructions you could verify: "run pnpm test:unit before committing" beats "test your changes." A copyable example and a keep-out list follow below.
What CLAUDE.md is for — and what it is not
CLAUDE.md is a markdown file that Claude Code loads at the start of every session. That is the whole mechanism. It is not a config file, not a system prompt, and not a guardrail.
Two consequences most guides skip:
It is advisory, not enforced. The docs are explicit: CLAUDE.md content is delivered as a user message after the system prompt, so there is no guarantee of strict compliance, especially for vague or conflicting instructions. If something must happen at a specific moment — a lint run before every commit, a blocked path — write a hook instead. Hooks execute regardless of what Claude decides.
Contradictions are worse than omissions. If two files give different guidance for the same behaviour, Claude may pick one arbitrarily. In a monorepo where several teams ship their own CLAUDE.md, this happens constantly.
Files are concatenated, not overridden. Claude walks up the directory tree and loads every CLAUDE.md and CLAUDE.local.md it finds, ordered from the filesystem root down — so the file nearest your launch directory is read last.
| Scope | Location | Who sees it |
|---|---|---|
| Managed policy | /etc/claude-code/CLAUDE.md (Linux), /Library/Application Support/ClaudeCode/CLAUDE.md (macOS) |
Everyone on the machine; cannot be excluded |
| User | ~/.claude/CLAUDE.md |
Just you, every project |
| Project | ./CLAUDE.md or ./.claude/CLAUDE.md |
The team, via version control |
| Local | ./CLAUDE.local.md |
Just you, this project — gitignore it |
A structure that survives a long project
Five blocks, in this order. The ordering matters because Claude scans structure the way readers do, and the earliest lines get the most reliable attention.
- What this is — one or two lines. Product, stack, versions.
- Commands — the exact invocations for build, test, lint, run, and deploy. Not "run the tests." The literal string.
- Layout — the three to five directories that matter and what lives in each. Point at files instead of describing them in prose.
- Conventions that differ from defaults — the ones a competent developer would guess wrong. If your linter already enforces it, leave it out.
- Traps — the things that have already broken. This section is the highest-value part of the file and the one people never write.
Anything that is a multi-step procedure, or that only matters in one corner of the codebase, does not go here. Procedures become skills, which load only when invoked. Local rules go in .claude/rules/ with a paths frontmatter field, so they enter context only when Claude touches matching files.
The full example you can copy
# CLAUDE.md
Billing service for qvib.pro. TypeScript 5.6, Node 22, Fastify, Postgres 16, Vitest.
## Commands
- Install: `pnpm install --frozen-lockfile`
- Dev: `pnpm dev` (needs a local Postgres on 5432)
- Unit tests: `pnpm test:unit`
- Integration tests: `pnpm test:int` — requires Docker running
- Lint + types: `pnpm check` — run this before every commit
- Migrations: `pnpm db:migrate`; never edit an applied migration, add a new one
## Layout
- `src/routes/` — HTTP handlers, one file per resource, thin
- `src/domain/` — business logic, no I/O, fully unit-tested
- `src/db/` — Drizzle schema and queries; schema is `src/db/schema.ts`
- `src/jobs/` — cron and queue workers
- `test/fixtures/` — shared factories, prefer these over inline objects
## Conventions
- Money is always integer minor units. Never floats. See `src/domain/money.ts`
- All timestamps are UTC `timestamptz`; format only at the edge
- Errors: throw `AppError` from `src/errors.ts`, never bare `Error`
- New endpoint = route + zod schema + integration test. All three or it is not done
## Traps
- `pnpm test` runs both suites and takes ~4 minutes. Use `test:unit` while iterating
- The Stripe webhook handler is idempotent by `event.id`. Do not add retries around it
- `src/db/schema.ts` is generated in part — check the header before editing
- Prod uses `NODE_ENV=production`, which disables the seed script. Do not "fix" this
<!-- Maintainer note: this file is reviewed at each release. Keep it under 200 lines. -->
That is roughly 40 lines and covers a real service. The HTML comment at the bottom is a genuine feature: block-level HTML comments are stripped before the content reaches Claude's context, so notes to human maintainers cost zero tokens.
If your repo already has an AGENTS.md, do not duplicate it. Claude Code reads CLAUDE.md, not AGENTS.md, so create a CLAUDE.md whose first line is @AGENTS.md and add Claude-specific instructions below. Our guide to portable agent instructions covers the cross-tool version, and Cursor rules work on a similar model.
What to keep out of it
This is where most CLAUDE.md files go wrong. Claude Code's own /doctor trim check encodes the heuristic: it removes content Claude can derive from the codebase — directory layouts, dependency lists, architecture overviews — and keeps pitfalls, rationale, and conventions that differ from tool defaults.
| Do not put in CLAUDE.md | Put it here instead | Why |
|---|---|---|
| Code style rules your linter enforces | .eslintrc, .prettierrc |
Claude runs the linter; duplicating it burns context every session |
| Full dependency lists, generated file trees | Nothing — delete it | Claude reads package.json faster than you can maintain a copy |
| Multi-step procedures (release flow, migration runbook) | A skill | Loads on demand instead of every session |
| Rules for one directory only | .claude/rules/*.md with paths frontmatter |
Enters context only when Claude opens matching files |
| Hard requirements ("never touch prod config") | A PreToolUse hook or permissions.deny |
CLAUDE.md is guidance; hooks are enforcement |
| Secrets, tokens, internal URLs | CLAUDE.local.md, gitignored |
Anthropic warns explicitly: it is a committed file |
| Long prose explaining your architecture philosophy | Your docs site | It does not change what Claude does |
One more trap: @path imports look like a way to shrink the file, and they are not. Imported files are expanded and loaded into context at launch alongside the file that references them, up to four hops deep. Imports help organisation, not context budget. Path-scoped rules and skills are the only mechanisms that actually defer loading.
Keeping it current
A CLAUDE.md rots faster than code: it describes intent rather than behaviour, and nothing fails when it drifts. Three habits keep it honest.
Write to it at the moment of correction. The trigger is simple — when Claude makes the same mistake a second time, or when you type a correction you also typed last session, that belongs in the file. Not later.
Verify it loaded. Run /context and check the Memory files list. A CLAUDE.md in the wrong directory silently does nothing, and this is the single most common cause of "Claude ignores my instructions." /memory lists and opens every memory file across scopes.
Prune on a schedule. Tie it to releases. Delete what is no longer true, merge duplicates, re-check the 200-line target. If it has grown past that, the answer is never a bigger file — it is moving sections into rules and skills.
Worth knowing: the project-root CLAUDE.md survives /compact — Claude re-reads it from disk and re-injects it. Nested files in subdirectories do not get re-injected; they reload the next time Claude reads a file in that directory. If an instruction seems to evaporate mid-session, that asymmetry is usually why.
Separately, Claude keeps its own auto memory per repository — a different system: Claude writes it, you did not, and only the first 200 lines or 25KB of its MEMORY.md index load per session. Do not confuse a fact Claude learned by itself with a rule you set. For prompt-level technique that complements the file, see our prompting guide.
FAQ
Where do I put the CLAUDE.md file?
Project root, as ./CLAUDE.md or ./.claude/CLAUDE.md — both work and both are committed for the team. Personal preferences that apply to every project go in ~/.claude/CLAUDE.md. Anything private to you on this one project goes in ./CLAUDE.local.md, which you should gitignore. All discovered files are concatenated in load order rather than overriding each other, so a user-level file and a project file both apply.
How long should CLAUDE.md be?
Anthropic recommends targeting under 200 lines per file. That is not an enforced limit — CLAUDE.md files load in full regardless of length — but longer files consume more context and measurably reduce how reliably Claude follows them. If you are past 200 lines, move sections into .claude/rules/ with path scoping or into skills, rather than trimming the useful parts.
Can I generate CLAUDE.md automatically?
Yes. /init analyses your codebase and writes a starter file with build commands, test instructions and discovered conventions. If a CLAUDE.md already exists, it suggests improvements instead of overwriting. It also reads existing Cursor rules (.cursor/rules/, .cursorrules) and Copilot instructions (.github/copilot-instructions.md) and folds the relevant parts in. Treat the output as a first draft — the traps section is the part /init cannot know.
Why is Claude ignoring my CLAUDE.md?
Check three things in order. Run /context and confirm the file appears under Memory files — if it is not there, Claude never saw it. Then look for contradictions between your project file, nested files in subdirectories, and .claude/rules/. Then make the instruction concrete enough to verify: "use 2-space indentation" works where "format code properly" does not. If the instruction must be guaranteed, it is not a CLAUDE.md instruction at all — it is a hook.
Do I need a separate CLAUDE.md per package in a monorepo?
Usually yes: a slim root file with shared commands and conventions, plus one per package. Subdirectory files load on demand when Claude reads files in those directories, so they cost nothing until relevant. If other teams' files in the tree are noise for your work, exclude them with claudeMdExcludes in .claude/settings.local.json.