How to Write a Great CLAUDE.md (with real before/after examples)
Most CLAUDE.md files I’ve seen do not work. They’re written like documentation — explaining the codebase, philosophizing about engineering values, listing “preferences we generally try to follow.” Claude reads them, then proceeds to do whatever feels statistically most common.
The fix is small but counterintuitive: write less, write harder, and put it in the right file.
This is what we learned writing CLAUDE.md for AI Memory Reader (Disclosure: we make this) — a macOS app that, by coincidence, exists specifically to let you browse other people’s CLAUDE.md files. We read a lot of them.
The hierarchy first (this matters more than people think)
Claude Code loads instructions from multiple files in a specific order. Higher-precedence files override lower ones:
- Managed policy —
/Library/Application Support/ClaudeCode/CLAUDE.md(macOS) or/etc/claude-code/CLAUDE.md(Linux). Set by an admin, cannot be excluded. - User global —
~/.claude/CLAUDE.md. Your personal preferences, applies to every project. - Project root —
./CLAUDE.mdor./.claude/CLAUDE.md. Committed to git. Team-shared. - Project local —
./CLAUDE.local.md. Personal project overrides, auto-gitignored. - Subdirectory — nested
CLAUDE.mdfiles apply to work in that subtree.
If you remember nothing else: personal preferences live globally; team-shareable conventions live in the project file. Mixing them is the most common mistake we see.
If a junior teammate clones the repo and gets your “always run prettier before commits” rule, that belongs in the project file. If they get your “respond in Cantonese and use US units,” that belongs in your global file. The repo’s CLAUDE.md is a team contract, not a personal config.
The single best rule about rule-writing
Write instructions Claude can verify it followed.
-
❌ “Format code properly”
-
✅ “Use 2-space indentation; tabs are forbidden”
-
❌ “Test your changes”
-
✅ “Run
npm run test:unitbefore every commit” -
❌ “Be careful with database queries”
-
✅ “All new database queries must go through
src/db/safe.ts; do not callknex.raw()directly”
The rule of thumb from Anthropic’s documentation: if a violation would make a code reviewer raise an eyebrow, it belongs in CLAUDE.md. If a violation would block CI, it belongs in CI, not CLAUDE.md. CLAUDE.md is for things humans care about that machines can’t auto-enforce.
Length matters — under 200 lines
Anthropic’s own guidance: keep CLAUDE.md under roughly 200 lines, and the rules section under 15 items. Longer files eat your context window for every single message, and counterintuitively get less adherence, not more.
If you have 30 rules, you haven’t done the work of deciding which 12 are load-bearing. The exercise of cutting is the exercise of finding the rules that matter.
Negative rules are as important as positive ones
This is the lesson most teams skip. Claude defaults to the most statistically common pattern — which is almost never your codebase’s pattern.
-
❌ Missing rule → Claude generates React class components because that’s still half the tutorials online.
-
✅ “No class components. Functional components with hooks only.”
-
❌ Missing rule → Claude writes
try { ... } catch (e) {}because that pattern is everywhere. -
✅ “Never catch and swallow errors. Either log to the bugsnag client or rethrow.”
Make the don’ts explicit. They’re often more important than the do’s.
Real before/after: a TypeScript backend repo
This is a CLAUDE.md from an actual production codebase (sanitized). The “before” was 287 lines; the “after” is 47.
Before
# Welcome to Our Codebase
This is a Node.js backend application built with TypeScript and Express. We follow modern JavaScript patterns and try to write clean, maintainable code. Our team values readability and testability above all else.
## Architecture
The codebase is organized into a layered architecture. We have controllers, services, and repositories. Controllers handle HTTP, services contain business logic, and repositories handle database access. We generally try to keep concerns separated.
## Coding Standards
- We use TypeScript with strict mode enabled
- We generally prefer functional patterns where it makes sense
- We try to write tests for new features when we can
- Code should be properly formatted
- Avoid using `any` unless necessary
- Use descriptive variable names
- Comments should explain why, not what
- ... (continues for 240 more lines)
After
# Engineering rules
## Stack
- TypeScript strict mode. `any` is forbidden — use `unknown` and narrow.
- Express 4.x, Knex for DB, Vitest for tests.
## Architecture (IMPORTANT)
- Controllers in `src/controllers/`. Thin — extract HTTP, call service.
- Services in `src/services/`. All business logic lives here.
- Repos in `src/repos/`. Only place that imports `knex`.
- A controller importing knex is a bug.
## Tests (YOU MUST)
- New service methods require a Vitest unit test.
- Run `npm run test:unit` before commit.
- Integration tests use real Postgres via `docker compose up test-db`. Never mock the DB.
## Forbidden patterns
- No `console.log` in committed code — use `src/lib/logger.ts`.
- No `try { ... } catch (e) {}` — either log or rethrow.
- No `Date.now()` in business logic — inject `Clock` from `src/lib/time.ts` (we test time-dependent code).
- No `process.env.X` outside `src/config.ts`.
## When in doubt
- New endpoint? Check `src/controllers/health.ts` for the canonical shape.
- New repo method? Check `src/repos/users.ts`.
Notice what’s gone: the philosophy, the “we generally try,” the welcome message, the architecture explanation. What’s left is enforceable. Every rule survives the “would a reviewer flag this in PR review?” test.
What does NOT belong in CLAUDE.md
Three categories we’d cut on sight:
1. Things visible in the code. “We use Express” — Claude can see package.json. “Our entry point is src/index.ts” — Claude can see the file tree. The CLAUDE.md is for things that cannot be inferred from reading the code: invariants, conventions, recent decisions, the location of subtle traps.
2. Things CI enforces. “Run prettier before commit” — if you have a pre-commit hook or CI check, Claude doesn’t need the rule. CLAUDE.md should not duplicate machine-enforceable rules. Keep it for the things only humans catch.
3. Implementation history. “We migrated from Redux to Zustand in March” — that’s a commit message, not a rule. Unless the migration left landmines (“don’t add Redux back; the old src/store/legacy/ directory should be considered read-only”), drop it.
Use the /memory command
Claude Code ships with /memory — it shows you exactly which CLAUDE.md files loaded in the current session, in order, with which scope. If a rule isn’t being followed, run /memory first. Often the answer is “the file you thought was loading didn’t load” (wrong directory, wrong name, gitignored when it shouldn’t be).
If you’re auditing CLAUDE.md files across many projects, AI Memory Reader gives you a one-pane view across your home directory — useful when you can’t remember which project has which rules. Disclosure: we make this; it’s free and GPL-3.0.
A note on length vs. tone
You can be terse without being curt. Compare:
Run npm test before commit.
Versus:
YOU MUST run
npm run test:unitbefore every commit. If the suite fails, fix the test rather than skipping it —--no-verifyis forbidden.
The second is longer but every word does work: the emphasis marker raises priority, the specific command removes ambiguity, the failure mode is preempted, and the escape hatch is closed.
Anthropic’s docs confirm what we observed in practice: IMPORTANT and YOU MUST markers measurably increase adherence. Use them sparingly — if everything is important, nothing is — but use them for the rules that have bitten you before.
Closing checklist
Before you commit a CLAUDE.md, run through:
- Under 200 lines total.
- Personal preferences are in
~/.claude/CLAUDE.md, not the project file. - Every rule is a direct command (no “we generally try”).
- Every rule is verifiable (no “be careful”).
- At least 30% of rules are negative (no X, never Y).
- Nothing is duplicated from CI/lint config.
-
IMPORTANTandYOU MUSTmarkers used on the 3-5 highest-stakes rules, no more. - You’ve run
/memoryand confirmed your file loads in the order you expected.
If your CLAUDE.md passes all eight, you’ve already done better than the median codebase we’ve seen.
Editorial note: this article is part of bestagent.dev — independent reviews of AI coding tools written by working engineers, not marketing teams. We make AI Memory Reader, which is mentioned twice above. No other tools mentioned here paid for placement.
Related reading
Reviews independently produced · Editorial policy
Read more reviews →