Writing a CLAUDE.md That Actually Works

Listen to this article
Click ▶ to start
0%

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:

  1. What — project-specific commands, structure, and environment quirks
  2. Why — the reasoning behind non-obvious architectural decisions
  3. 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:

LocationScopeNotes
~/.claude/CLAUDE.mdAll sessions, all projectsPersonal preferences and global rules
./CLAUDE.mdProject-wideCommit to git — shared with the whole team
./CLAUDE.local.mdProject-wide, personalAdd to .gitignore — not shared
./subdir/CLAUDE.mdLoads when editing files in that directoryGreat 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

  1. Write the rule
  2. Start a fresh session (/clear or close and reopen)
  3. Give Claude a task where the rule applies
  4. Check whether Claude’s output reflects the rule
  5. 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.

IncludeExclude
Bash commands Claude can’t guessAnything Claude can figure out by reading code
Code style rules that differ from defaultsStandard language conventions Claude already knows
Testing instructions and preferred test runnersDetailed API documentation (link to docs instead)
Repository etiquette (branch naming, PR conventions)Information that changes frequently
Architectural decisions specific to your projectLong explanations or tutorials
Developer environment quirks (required env vars)File-by-file descriptions of the codebase
Common gotchas or non-obvious behaviorsSelf-evident practices like “write clean code”
Model routing guidelinesTechnology 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:

ToolPurposeWhen to use
CLAUDE.mdPersistent context, always loadedConventions and rules that apply to every session
Skills (.claude/skills/)Domain knowledge, loaded on demandWorkflows and deep context for specific tasks
Hooks (.claude/settings.json)Deterministic enforcementActions that must happen every time with no exceptions
Subagents (.claude/agents/)Isolated specialist contextTasks 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.mdPut it in a rules/ file
Universal: applies to every taskDomain-specific: only relevant to one area
Short: under 50 linesDetailed: can be longer because it loads conditionally
Foundational: other rules depend on itSelf-contained: works without other context
Security / legal: must never be missedPerformance / 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

LayerWhat it isWhen it loadsYou control it?
CLAUDE.mdYour rules — commit to gitEvery session, alwaysYes — edit the file
Auto Memory (MEMORY.md)Claude’s observations about you and your project — written by ClaudeFirst 200 lines at startup; rest on-demand per topicYes — but Claude writes it
Session MemoryThe conversation history from your last sessionOnly 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:

  1. Claude makes an unexpected decision → add a rule
  2. Rule already being followed correctly → delete the rule (wasting a slot)
  3. Domain knowledge growing the file → move it to a Skill
  4. Rule must always happen → convert it to a Hook
  5. 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.mdAuto Memory
PurposeBehaviour rules and conventionsFacts and preferences learned over time
Who writes itYou, deliberatelyClaude, from sessions
ScopeShared with the team (if committed)Personal (in your home directory)
Token costEvery sessionEvery 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.

Abhay

Abhay Pratap Singh

DevOps Engineer passionate about automation, cloud infrastructure, and self-hosted tools. I write about Kubernetes, Terraform, DNS, and everything in between.