Writing a CLAUDE.md That Actually Works
Every CLAUDE.md file gets loaded into context on every session. Most teams treat it like documentation — a place to describe the project, list the tech stack, explain what the tests do. That is the wrong mental model and it is why most CLAUDE.md files are both too long and too ineffective.
CLAUDE.md is behavioral programming. Its job is to change how Claude makes decisions, not to describe facts that Claude can read from the codebase itself. Claude is stateless — it has nothing about your project at the start of each session. CLAUDE.md is the primary onboarding mechanism that enters every conversation.
The Core Mental Model
flowchart LR
subgraph Wrong[Wrong approach - Documentation]
D1[What React is]
D2[Tech stack list]
D3[What the tests do]
D4[Architecture overview]
end
subgraph Right[Right approach - Behavioral programming]
R1[Commands Claude cannot guess]
R2[Conventions that differ from defaults]
R3[Decision rules for ambiguous situations]
R4[Known gotchas and failure modes]
end
Wrong -->|Wastes tokens\nClaude ignores it| X[Poor results]
Right -->|Shapes decisions\nHighly actionable| Y[Consistent results]
Claude already knows what React is. It can read your package.json to learn your stack. It understands standard testing patterns. Writing those things in CLAUDE.md wastes context without influencing behaviour.
What Claude cannot know without being told: the custom deploy command your team built, the specific reason you chose a non-standard folder structure, the production database host name format, the fact that your auth service requires a specific environment variable to initialise.
A good CLAUDE.md breaks down into three layers:
- What — project-specific commands, structure, and environment quirks
- Why — the reasoning behind non-obvious architectural decisions
- How — the specific ways Claude should operate within your project
The Instruction Budget
Before writing a single line, understand the constraint you are working within.
Claude Code’s built-in system prompt already consumes roughly 50 instructions before you write a word. Research shows frontier LLMs can follow approximately 150–200 total instructions with reasonable consistency before degradation sets in. That leaves you 100–150 slots for your project-specific rules.
A bloated CLAUDE.md does not just waste space — it competes with your actual rules. When the file is too long, Claude begins filtering what it applies. Rules near the bottom are the first casualties. If Claude keeps doing something you have a rule against, the file is probably too long and the rule is getting lost.
The practical target: under 200 lines. Some experienced teams run under 60. Anthropic’s own internal teams keep their CLAUDE.md around 100 lines.
File Locations
CLAUDE.md files can live in several places and Claude loads them based on what you are editing:
| Location | Scope | Notes |
|---|---|---|
~/.claude/CLAUDE.md | All sessions, all projects | Personal preferences and global rules |
./CLAUDE.md | Project-wide | Commit to git — shared with the whole team |
./CLAUDE.local.md | Project-wide, personal | Add to .gitignore — not shared |
./subdir/CLAUDE.md | Loads when editing files in that directory | Great for monorepos |
More specific files override more general ones. Use CLAUDE.local.md for anything personal: your preferred verbosity level, tools you like, local path overrides. Your team does not need to see those.
Parent directory files also load automatically. In a monorepo, both root/CLAUDE.md and root/services/api/CLAUDE.md are pulled in when you edit a file inside root/services/api/.
Structure That Works
Here is a template that covers what matters without bloating the file:
# Project: Platform API
## Commands
Commands Claude cannot guess from reading the code:
- Build: `npm run build`
- Tests: `npm test` — run a single test with `npm test -- --grep "auth"`
- Staging deploy: `./scripts/deploy.sh staging`
- Database migrations: `npm run db:migrate`
- Seed development data: `npm run db:seed`
## Directory Structure
src/
api/ # REST endpoints — one file per resource
lib/ # Pure utility functions, no side effects
db/queries/ # Raw SQL only, no ORM
auth/ # Auth service — requires NODE_ENV=production
## Conventions (where we differ from defaults)
- 4-space indentation, not 2
- camelCase for functions and variables, PascalCase for classes and types
- ES modules only — never use CommonJS require()
- All async functions must have explicit error handling — no unhandled rejections
## Architecture Decisions
- Raw SQL only in db/queries/ — we evaluated Prisma and rejected it for performance reasons
- Tests colocate with source: `auth.ts` → `auth.test.ts` in the same folder
## Model Routing
- Haiku: renaming, formatting, simple lookups, one-liners
- Sonnet: standard implementation, debugging, code review, tests
- Opus: architecture decisions, complex refactors, security analysis
## Known Gotchas
- Auth service requires NODE_ENV=production to initialise — set it in tests
- Database connections pool at 10 — do not exceed in tests or you get timeouts
- The payment processor sandbox rejects amounts over $10,000 — use $99.99 in tests
- Migrations do not auto-run in tests — call `db.migrate()` manually in test setup
## Git
- Feature branches: `feature/description`
- All changes via PR — never push to main directly
- PR titles must include the ticket number: `PLAT-123: description`
## Compact Instructions
When compacting, always preserve: the full list of modified files, test commands, and any open decisions.
The ## Compact Instructions section is worth calling out: it tells Claude what to preserve when the context window auto-compacts during long sessions, so critical state survives summarisation.
Imports: Keep CLAUDE.md Lean
CLAUDE.md supports @path/to/file imports. Use them to reference existing documents instead of duplicating content:
# Project: Platform API
See @README.md for project overview and @package.json for available npm commands.
## Additional Conventions
- Git workflow: @docs/git-instructions.md
- Security rules: @docs/security-checklist.md
- Personal overrides: @~/.claude/my-project-instructions.md
## Known Gotchas
- Auth service requires NODE_ENV=production to initialise — set it in tests
- Migrations do not auto-run in tests — call `db.migrate()` manually in test setup
One important caveat: @ imports embed the entire file at load time. Importing a 500-line document burns your instruction budget before your actual rules load. For large documents, use descriptive text links instead of @ imports:
# Project: Platform API
## Additional Context
For migration procedures, see `docs/migrations.md`.
For API design conventions, see `docs/api-design.md`.
This way Claude fetches the file only when relevant to the current task, rather than loading it into every session.
What to Include vs What to Skip
flowchart TD
Q[New piece of information] --> A{Can Claude learn this\nby reading the codebase?}
A -->|Yes - it is in the code| Skip[Skip it\nWaste of context]
A -->|No - it requires\nexternal knowledge| B{Does it affect\nhow Claude makes decisions?}
B -->|No - it is just a fact| Skip
B -->|Yes - it changes behaviour| Include[Include it\nHigh value]
Apply the removal test before every line: “Would removing this cause Claude to make mistakes?” If the answer is no, cut it. If Claude already does something correctly without the instruction, it is wasting a slot.
| Include | Exclude |
|---|---|
| Bash commands Claude can’t guess | Anything Claude can figure out by reading code |
| Code style rules that differ from defaults | Standard language conventions Claude already knows |
| Testing instructions and preferred test runners | Detailed API documentation (link to docs instead) |
| Repository etiquette (branch naming, PR conventions) | Information that changes frequently |
| Architectural decisions specific to your project | Long explanations or tutorials |
| Developer environment quirks (required env vars) | File-by-file descriptions of the codebase |
| Common gotchas or non-obvious behaviors | Self-evident practices like “write clean code” |
| Model routing guidelines | Technology definitions and descriptions |
Writing craft tip: every “never” should be paired with an alternative direction. “Never use string interpolation in SQL queries” is weak on its own. “Never use string interpolation in SQL queries — use parameterised queries: db.query(sql, [params])” gives Claude somewhere to go. Prohibitions without alternatives leave Claude stuck.
To improve adherence to critical rules, mark them explicitly: IMPORTANT: Never commit secrets to git or YOU MUST validate user input at the API boundary. Use this sparingly — emphasis scales poorly. If every rule is marked important, the emphasis becomes invisible and stops working.
CLAUDE.md vs Skills vs Hooks vs Subagents
These four tools are often confused. They serve different purposes:
| Tool | Purpose | When to use |
|---|---|---|
CLAUDE.md | Persistent context, always loaded | Conventions and rules that apply to every session |
Skills (.claude/skills/) | Domain knowledge, loaded on demand | Workflows and deep context for specific tasks |
Hooks (.claude/settings.json) | Deterministic enforcement | Actions that must happen every time with no exceptions |
Subagents (.claude/agents/) | Isolated specialist context | Tasks that read many files or need specialised focus |
CLAUDE.md instructions are advisory — Claude reads them and applies judgement. Hooks are deterministic — they run regardless of what Claude decides. If you want a linter to run after every file edit, a hook guarantees it. A CLAUDE.md instruction asking Claude to run the linter is a suggestion.
Skills
Use Skills for task-specific knowledge that would bloat CLAUDE.md if it were always loaded: a deep guide to your API design conventions, a workflow for fixing GitHub issues, a checklist for database migrations. Skills load when relevant and keep your base context lean.
Create a skill by adding a directory with a SKILL.md to .claude/skills/:
---
name: fix-github-issue
description: Fix a GitHub issue end-to-end with tests and PR
disable-model-invocation: true
---
Analyze and fix the GitHub issue: $ARGUMENTS.
1. Use `gh issue view` to get the issue details
2. Understand the problem described in the issue
3. Search the codebase for relevant files
4. Implement the necessary changes to fix the issue
5. Write and run tests to verify the fix
6. Ensure code passes linting and type checking
7. Create a descriptive commit message
8. Push and create a PR
Run /fix-github-issue 1234 to invoke it. Use disable-model-invocation: true for workflows with side effects you want to trigger explicitly.
Subagents
Subagents run in their own isolated context with their own set of allowed tools. They are useful for tasks that read many files without cluttering your main conversation. Create one in .claude/agents/:
---
name: security-reviewer
description: Reviews code changes for security vulnerabilities
tools: Read, Grep, Glob, Bash
model: claude-opus-4-7
---
You are a senior security engineer. Review code for:
- Injection vulnerabilities (SQL, XSS, command injection)
- Authentication and authorization flaws
- Secrets or credentials in code
- Insecure data handling
Provide specific file:line references and suggested fixes.
Invoke directly: “Use the security-reviewer subagent to review these changes.” Because subagents run in separate context windows, they are also useful for quality-focused workflows — a fresh context improves code review since the reviewer is not biased toward code it just wrote.
Path-Scoped Rules for Large Projects
For monorepos or projects with multiple distinct areas, use path-scoped rule files instead of one giant CLAUDE.md:
.claude/
├── CLAUDE.md ← global rules (always loaded)
└── rules/
├── api.md ← loads when editing src/api/**
├── infra.md ← loads when editing infra/**
└── frontend.md ← loads when editing src/frontend/**
Each file includes a frontmatter paths section:
---
paths:
- "src/api/**/*.ts"
---
# API Rules
- All endpoints must validate the Authorization header before any business logic
- Return 422 (not 400) for validation errors — include field-level error details
- Never return stack traces in error responses
- Log request IDs for all errors using logger.error({ requestId, error })
This keeps each rule set small and relevant. Rules about API security do not load when Claude is editing Terraform configs.
For monorepos where you want to prevent other teams’ rules from loading, use claudeMdExcludes in .claude/settings.local.json (outside version control):
{
"claudeMdExcludes": [
"**/other-team/.claude/rules/**"
]
}
The Memory System
For persistent context across sessions, Claude Code supports auto memory in ~/.claude/projects/<project>/memory/. These are markdown files that Claude reads and writes automatically to remember facts about your project and your preferences.
Organise memory by type:
~/.claude/projects/my-project/memory/
├── MEMORY.md ← index file with pointers
├── user_prefs.md ← your personal preferences
├── feedback.md ← corrections and lessons learned
├── project_ctx.md ← project-specific context
└── decisions.md ← architectural decisions reached
Sample feedback.md:
---
name: Feedback - Database queries
type: feedback
---
Always use parameterised queries in this project, never string interpolation.
**Why:** A previous incident where a dynamic filter was built with string concatenation
caused a data leak in the staging environment.
**How to apply:** Any time writing a database query, use `db.query(sql, [params])` not
`db.query(sql + userInput)`.
The “Why” and “How to apply” sections matter — they help Claude judge edge cases rather than blindly following a rule. Check what Claude has stored with /memory. Edit or remove stale entries the same way you would prune CLAUDE.md.
Session Patterns That Compound Over Time
The Session Retrospective
At the end of each meaningful session, ask Claude to summarise what it learned:
What did you learn this session that should be persisted?
- General concepts → CLAUDE.md
- Architectural choices → decision records in docs/
- Technical skills → skill files in .claude/skills/
- Personal preferences → memory files
Documentation that persists between sessions means Claude avoids repeating the same mistakes. Over weeks, this compounds into a genuinely sharp project assistant.
Never Patch Bugs Yourself Mid-Session
When Claude misses a bug you spot, resist the urge to fix it directly. Instead, have Claude investigate and document the cause. Since documentation persists across sessions, this builds institutional knowledge the agent can draw on later. Quick fixes that bypass investigation lose the lesson.
The Clear/Rewind Pattern
Rather than correcting Claude mid-conversation, use /rewind or /clear to return to the last stable state when something goes wrong. Flawed responses stay in context and pollute subsequent attempts. Two failed corrections in a row is a signal to clear context and re-prompt with what you learned from those failures.
Parallel Sessions: Writer / Reviewer
Run multiple Claude sessions in parallel for quality-critical work:
| Session A (Writer) | Session B (Reviewer) |
|---|---|
Implement a rate limiter for the API endpoints | |
Review @src/middleware/rateLimiter.ts — look for edge cases, race conditions, and consistency with existing middleware patterns | |
Here is the review feedback: [output]. Address these issues. |
A fresh context improves review quality because the reviewer is not biased toward code it just wrote.
Common Mistakes
1. Writing for humans, not for Claude
Your team lead reads CLAUDE.md and adds context like “We switched from Mongoose to Prisma in 2023 because of type safety issues.” Interesting history, but it gives Claude no actionable instruction. Cut it.
2. Contradicting yourself
“Always write tests before code” and “tests are optional for hotfixes” in the same file creates inconsistent behaviour. Claude will apply one or the other unpredictably. Pick one and remove the other.
3. Exceeding the instruction budget
With ~50 instructions already in the system prompt and a cap of ~200 before degradation, you have roughly 150 slots. A 300-line CLAUDE.md likely burns most of them. Some rules at the bottom will be routinely ignored. Better to have 80 lines that are always followed than 300 lines that are sometimes followed.
4. Stale rules
CLAUDE.md needs maintenance. Review it every two weeks and remove anything that is no longer true. Stale rules reduce trust in the rules that remain. Treat it like code: review it when Claude makes an unexpected decision, prune it regularly, and verify that changes actually shift Claude’s behaviour. Add this standing instruction to CLAUDE.md itself: "When you encounter a bad assumption, suggest a correction to this file."
5. Accumulation without pruning
The most common long-term failure mode: rules pile up, nobody prunes, Claude filters out half of them. A rule that Claude already follows correctly without being told is wasting a slot. Every few weeks, audit the file and delete anything that passes the removal test.
6. Using CLAUDE.md for enforcement
If a rule must happen every time with zero exceptions — running the linter, blocking writes to the migrations folder, posting a Slack notification — use a hook. CLAUDE.md instructions are advisory. Hooks are guarantees. Claude Code lets you ask it to write hooks for you: “Write a hook that runs eslint after every file edit.”
7. Large @ imports burning instruction budget
Importing a large file with @ embeds its entire content at session start. A 500-line architecture doc imported in CLAUDE.md costs you context on every single session, even when you are not working on that area. Use descriptive text references instead; Claude fetches the file only when needed.
8. Adding rules before Claude makes mistakes
Add rules to CLAUDE.md only in response to actual mistakes, not hypothetical ones. Speculative rules add noise and rarely trigger. Wait until Claude does something wrong, then codify the correction.
Quick Start
Run /init in any project and Claude will generate a starter CLAUDE.md based on what it can read from your codebase. Read every line of the output. Remove the generic parts. Add your specific commands and gotchas. You will have a solid file in under 15 minutes.
Then keep the feedback loop going:
- Whenever Claude makes an assumption you want to correct, add it to CLAUDE.md
- At the end of each session, run a retrospective and persist what was learned
- Review the file every two weeks — prune anything that passes the removal test
- When a rule must be guaranteed, convert it to a hook
- When domain knowledge is growing the file, move it to a skill
Every mistake becomes a rule. Every rule that is no longer needed gets cut. The longer a team works this way, the sharper the agent gets in that specific codebase.
A Note on Auto Memory
Claude Code maintains a persistent memory system at ~/.claude/projects/<project>/memory/. Across sessions, Claude reads these files to recall facts about your project and your preferences — without you repeating them.
This is complementary to, not a replacement for, CLAUDE.md:
| CLAUDE.md | Auto Memory | |
|---|---|---|
| Purpose | Behaviour rules and conventions | Facts and preferences learned over time |
| Who writes it | You, deliberately | Claude, from sessions |
| Scope | Shared with the team (if committed) | Personal (in your home directory) |
| Token cost | Every session | Every session |
The key distinction: CLAUDE.md sets the rules for how Claude should work in your project. Auto memory records what Claude has learned about you and your project — specific facts, corrections you have made, preferences you have stated. Both load every session, so keeping both lean matters.
