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 common failure mode: your CLAUDE.md grows into a 2,000-line monster. Claude reads all of it on every prompt. Slow, expensive, and half of it is not even relevant. The solution is to break that monolith into three lean, purpose-built systems:
- Modular Rules — small, scoped rule files that load only when relevant
- Auto Memory — a notebook Claude writes and reads automatically so you never have to repeat yourself
- Skills — deep expertise Claude loads on demand, not on every prompt
This post covers CLAUDE.md itself. The rule for each section: if it does not change how Claude makes a decision, cut it.
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.
Output Styles: Teaching Claude How to Talk to You
One of the most underused capabilities in CLAUDE.md is controlling how Claude communicates — not just what it does, but how it responds. This is called output style configuration, and getting it right eliminates a surprising amount of back-and-forth.
Add an ## Output Style section to your CLAUDE.md:
## Output Style
- Be terse and direct — no filler phrases, no "Great question!", no "Certainly!"
- When making code changes, show the diff only — do not re-print the whole file
- When explaining decisions, lead with the decision, then the reason
- For error messages, always include: what failed, why, and the exact command to fix it
- Do not add a trailing summary of what you just did — I can read the change
- When you are uncertain, say so explicitly — do not guess and present it as fact
- Ask one clarifying question at a time, not a list of five
Or the opposite — if you prefer more explanatory responses:
## Output Style
- Explain your reasoning before making changes, especially for architectural decisions
- After completing a task, summarise what changed and why
- When I ask about an error, include context about what might have caused it
- Use code comments to explain non-obvious logic you add
The key insight: Claude defaults to a balanced verbosity that works for most people but is perfect for nobody. An explicit output style instruction takes about 5 lines and eliminates the most common frustrations — over-explanation when you just want the fix, under-explanation when you need to understand the decision.
Verbosity per task type
You can specify different verbosity for different contexts:
## Output Style
- Code changes: minimal output — diff only, no explanation unless I ask
- Architecture discussions: detailed — include trade-offs and alternatives
- Error diagnosis: step by step — show your reasoning so I can verify it
- Refactoring: list the files changed and why, then show diffs
- Tests: show what scenarios are covered and what is explicitly not covered
Controlling response format
## Output Style
- Use markdown headers for any response over 3 paragraphs
- Use code blocks for all commands — even one-liners
- For tables, use markdown tables not plain text
- Mermaid diagrams for architecture, never ASCII art
- Numbered lists for sequential steps, bullet lists for options
Before vs After: Real Examples
Abstract rules are easy to forget. Here are concrete before/after pairs showing what the rules look like in practice.
Bad: Too much narrative
## About the Project
This is a Node.js REST API built with Express that handles user authentication
and manages subscription billing. The project uses TypeScript for type safety
and was started in 2022. We use PostgreSQL as our primary database because
of its reliability and the team's familiarity with it. Tests are written
with Jest and we follow the AAA pattern (Arrange, Act, Assert).
Why it fails: Claude already knows what Node.js, Express, TypeScript, PostgreSQL, and Jest are. Nothing here changes a decision Claude would otherwise make incorrectly.
Good: Commands and gotchas only
## Commands
- Build: `npm run build`
- Test: `npm test` — single test: `npm test -- --testNamePattern "auth"`
- Migrate: `npm run db:migrate` — does NOT auto-run in tests, call manually
- Lint: `npm run lint:fix` (not `lint` — that one only reports, does not fix)
## Gotchas
- Auth service requires `NODE_ENV=production` to initialise — even in test files
- DB pool max is 10 — never exceed in concurrent tests or you get ECONNREFUSED
- Payment sandbox rejects amounts over $10,000 — use 9999 in tests
Why it works: Every line changes what Claude would do without it. The npm run lint vs npm run lint:fix distinction alone prevents broken CI runs.
Bad: Self-evident convention
## Code Quality
- Write clean, readable code
- Use meaningful variable names
- Handle errors properly
- Add comments where necessary
- Follow SOLID principles
Why it fails: These apply to every codebase and Claude already does them. Zero decision impact.
Good: Deviations from defaults
## Conventions (where we differ from defaults)
- 4-space indentation — our .editorconfig enforces this
- Named exports only — no default exports anywhere in the codebase
- Error handling: use Result<T, E> from `neverthrow` — no try/catch in business logic
- All database queries in `src/db/queries/` only — never inline SQL in service files
- camelCase for files: `userService.ts` not `user-service.ts`
Why it works: Every rule here deviates from what Claude would naturally do. Without these, Claude would use default exports, try/catch, and kebab-case file names.
Bad: History instead of instruction
## Architecture
We evaluated several ORMs including Prisma, TypeORM, and Sequelize before
settling on raw SQL. The main reasons were performance, the team's SQL expertise,
and avoiding the abstraction leakiness we experienced with ORMs in previous projects.
Why it fails: Informative, but gives Claude no decision guidance.
Good: Decision + rule
## Architecture Decisions
- Raw SQL only in `src/db/queries/` — no ORM, no query builder
- DO NOT suggest Prisma or TypeORM — evaluated and rejected
- New queries follow the pattern in `src/db/queries/users.ts`
- REST only — no GraphQL. Reason: team expertise, not a tech constraint
Why it works: The explicit “DO NOT suggest” is the key addition. Without it, Claude will recommend ORMs when discussing database work because that is usually the right call.
Testing Your Rules
Adding a rule to CLAUDE.md is not the same as the rule being followed. Test each rule you add:
The verification method
- Write the rule
- Start a fresh session (
/clearor close and reopen) - Give Claude a task where the rule applies
- Check whether Claude’s output reflects the rule
- If it does not: the rule is either buried too deep, worded too weakly, or contradicted elsewhere
Common failure patterns
Rule is buried: Move the rule earlier in the file. Rules in the first 100 lines are followed more consistently than rules at line 180.
Rule is too weak: “Prefer parameterised queries” is soft — Claude may override it when it decides the alternative is acceptable. “NEVER use string interpolation in SQL — always use parameterised queries” is hard.
Rule is contradicted: Search the file for any instruction that could be interpreted as an exception to your rule. Pick one direction and remove the other.
Rule is already followed by default: Claude will follow standard best practices without being told. Test whether the rule changes anything. If Claude behaves the same way without the rule, delete the rule.
The self-correction instruction
Add this line to your CLAUDE.md and it will catch stale rules automatically:
## Meta
When you encounter a CLAUDE.md rule that seems incorrect or outdated based on
what you observe in the codebase, say so explicitly and suggest a correction.
This turns Claude into a co-maintainer of the file — it flags contradictions rather than silently ignoring them.
Personal Global CLAUDE.md
The ~/.claude/CLAUDE.md file loads in every project and every session. Most developers leave it empty. That is a missed opportunity.
Your global CLAUDE.md is the right place for:
- Personal preferences that apply everywhere — verbosity, formatting, response style
- Model routing defaults you always want
- Tools and workflow preferences that are about you, not any specific project
- Things you are tired of repeating at the start of every session
Example ~/.claude/CLAUDE.md:
# Personal Preferences (Abhay)
## Output Style
- Be direct and terse — no filler, no trailing summaries
- When making code changes, show what changed and why in one line — not a paragraph
- If you are uncertain about something, say "I'm not sure — here is my best guess"
- Never use emoji in code comments or documentation
## Model Routing
- Default to Sonnet for everything
- Switch to Opus automatically for: architecture decisions, security analysis,
anything involving production data
- Use Haiku for: renaming, reformatting, simple one-liner lookups
## Work Style
- Before making large changes, outline the plan and ask me to confirm
- When I say "fix this", fix the specific thing — do not refactor surrounding code
- Run tests before reporting a task as complete
- If tests fail, investigate and fix before telling me the task is done
## Always
- Use British English spelling (colour, behaviour, favour)
- Prefer `const` over `let` unless mutation is required
- Never suggest adding a dependency for something achievable in 10 lines of standard library
Keep it under 60 lines. Anything project-specific goes in the project’s CLAUDE.md. Global rules should apply to everything you do, everywhere.
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
The Priority Saturation Problem
A CLAUDE.md that grows to 400+ lines with all topics mixed together stops working. When React rules, database rules, API rules, and infrastructure rules all load every session, Claude treats every instruction as equally important — which is the same as treating nothing as important. The rules compete with each other and with the conversation context.
Before: monolithic CLAUDE.md (400 lines, always consumed)
.claude/
└── CLAUDE.md ← React rules, DB rules, API rules, infra rules, test rules... all 400 lines
Every session loads 400 lines of context — even when you are only editing a Terraform file and the React and test rules are irrelevant.
After: modular rules (only ~100 lines active at any time)
.claude/
├── CLAUDE.md ← 50 lines: universal rules only
└── rules/
├── api.md ← 50 lines: loads when editing src/api/**
├── frontend.md ← 50 lines: loads when editing src/frontend/**
├── infra.md ← 50 lines: loads when editing infra/**
└── tests.md ← 50 lines: loads when editing **/*.test.*
Total words on disk: 250 lines. Active at any time: ~100 lines (CLAUDE.md + one scoped file). Signal density goes up, cost goes down.
Always-Loaded vs Path-Scoped Rules
Always-loaded — no frontmatter, loads every session:
<!-- .claude/rules/git-workflow.md — no frontmatter = always loaded -->
# Git Workflow
- Never commit to main directly
- Branch naming: feat/, fix/, chore/ prefixes
Path-scoped — frontmatter with paths: key, loads only when Claude works on matching files:
---
paths:
- "src/api/**/*.ts"
- "src/api/**/*.js"
---
# 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 })
The paths: key uses glob patterns. When Claude reads or edits a file matching any pattern, the rule file loads automatically. When it is not working on matching files, the rule is invisible and consumes no context.
The decision table:
| Put it in CLAUDE.md | Put it in a rules/ file |
|---|---|
| Universal: applies to every task | Domain-specific: only relevant to one area |
| Short: under 50 lines | Detailed: can be longer because it loads conditionally |
| Foundational: other rules depend on it | Self-contained: works without other context |
| Security / legal: must never be missed | Performance / style: helpful but not critical |
Personal rules across all projects — place rules in ~/.claude/rules/ (user-level) to apply them everywhere, not just in one project. These load alongside CLAUDE.md in every session regardless of which project you open. Good for personal style preferences you want universally: preferred comment style, your go-to test patterns, tools you always use.
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
Claude Code has three distinct layers of memory. Understanding which is which prevents confusion when Claude seems to “forget” something or “remember” something you did not tell it.
The Three Layers
| Layer | What it is | When it loads | You control it? |
|---|---|---|---|
| CLAUDE.md | Your rules — commit to git | Every session, always | Yes — edit the file |
| Auto Memory (MEMORY.md) | Claude’s observations about you and your project — written by Claude | First 200 lines at startup; rest on-demand per topic | Yes — but Claude writes it |
| Session Memory | The conversation history from your last session | Only on claude -c (resume) | No — auto-managed |
Think of CLAUDE.md as the textbook you hand Claude before it starts. Auto Memory is the notebook it keeps for itself. Session Memory is what it remembers from your last conversation.
Layer 1: CLAUDE.md — your instructions, committed to git, shared with your team, loaded every session.
Layer 2: Auto Memory — Claude writes what it observes (“user prefers pytest over unittest”, “this project uses custom error types from lib/errors.ts”) into ~/.claude/projects/<project>/memory/. The MEMORY.md index file is loaded in full at startup. Individual topic files (e.g., user_prefs.md, feedback.md) are loaded on demand as they become relevant. The first 200 lines of MEMORY.md are always in context — Claude uses the index to decide which topic files to pull in.
Layer 3: Session Memory — when you run claude -c to resume, the conversation history from your previous session is restored. Permissions and session-scoped settings reset (you re-approve them), but Claude’s understanding of what you were working on comes back.
Auto Memory Structure
~/.claude/projects/my-project/memory/
├── MEMORY.md ← index file — first 200 lines loaded at startup
├── 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 applying a rule.
Triggering Memory Explicitly
Auto Memory doesn’t only rely on Claude’s own judgment — you can instruct it directly in plain English:
- “remember that we use pnpm, not npm”
- “save to memory that the API tests require a local Redis instance”
- “note that the staging environment uses port 3001”
Claude writes the fact into its memory files immediately. This is the fastest way to seed memory for something you know Claude will need repeatedly.
Watching Memory in Action
When Claude interacts with its memory files, you see status messages in the Claude Code interface:
- “writing memory” — Claude is saving something it learned
- “recalled memory” — Claude is reading from its saved notes
These are the visual cues that Auto Memory is working. If you never see them, either auto memory is disabled or the session hasn’t produced anything worth persisting yet.
Checking and Editing Memory
/memory
The /memory command opens an interactive selector that shows:
- All CLAUDE.md files currently loaded in your session (project, user, rules, etc.)
- The Auto Memory toggle — an ON/OFF switch
- A link to open the auto memory folder so you can browse what Claude has saved
From here you can toggle auto memory on/off, open any memory file in your editor, and browse all saved notes. Edit or delete stale entries directly in the files — same as editing CLAUDE.md. If Claude has learned something wrong, correct it and tell Claude you did so.
One important note: memory is scoped to a directory, not a git branch. All branches and worktrees of the same repo share one memory directory. This is usually what you want — your preferences apply regardless of branch. If you need branch-specific memory (rare), use CLAUDE.md instead, since that is committed to git and can differ per branch.
Configuring Auto Memory
// ~/.claude/settings.json (user-level)
{
"autoMemoryEnabled": false // disable auto memory entirely
}
Or via environment variable (useful in CI/CD):
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 claude -p "..."
For a custom memory directory (e.g., keeping all Claude memory in a shared Dropbox folder):
{
"autoMemoryDirectory": "/Users/you/Dropbox/claude-memory/my-project"
}
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
Step 1: Generate a starter file
claude
/init
Claude reads your project and generates a starter CLAUDE.md. Do not accept it as-is — this is a first draft, not a finished file.
Step 2: Prune the output
Read every line of the generated file. For each line, ask the removal test question: “Would removing this cause Claude to make a mistake?” Delete every line where the answer is no. You will typically cut 40–60% of the generated content.
Step 3: Add what the generator missed
The generator can read your code but cannot know:
- Why you made unusual architectural choices
- What commands you actually run (vs what the README says)
- The production gotchas your team has discovered the hard way
- Your personal output style preferences
Add those now. They are the highest-value lines in the file.
Step 4: Add an Output Style section
Even 5 lines here saves hours of back-and-forth over a month:
## Output Style
- Be direct — no filler phrases, no trailing summaries of what you just did
- Show diffs, not full file reprints
- If uncertain, say so — do not guess and present it as fact
Step 5: Test the file
Close the session, reopen it, and give Claude a task where your rules apply. If the output does not reflect your rules, the rule is either too weak, too buried, or contradicted elsewhere.
The ongoing loop:
- Claude makes an unexpected decision → add a rule
- Rule already being followed correctly → delete the rule (wasting a slot)
- Domain knowledge growing the file → move it to a Skill
- Rule must always happen → convert it to a Hook
- Review every two weeks — prune anything stale
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.
