Chapter 2: CLAUDE.md & the Memory System
Learning Objectives
- Understand why CLAUDE.md is the single most important file in your project (for Claude Code)
- Write a production-grade CLAUDE.md inspired by real projects like gstack (68k stars)
- Set up the four-layer configuration hierarchy: project, local, user, path rules
- Experience auto-memory across sessions
- Use path-specific rules for surgical control
Concepts
The Problem CLAUDE.md Solves
Without a CLAUDE.md, every session starts from zero. Claude doesn't know your tech stack, your naming conventions, your branching strategy, or that your team will reject any PR that uses var instead of const.
Without CLAUDE.md:
You: "Write a component"
Claude: (uses class components -- your project is all hooks)
You: "No, use hooks"
Claude: (writes CSS modules -- you use Tailwind)
You: "No, Tailwind"
You: (third try, finally right, but you wasted 10 minutes)
With CLAUDE.md:
CLAUDE.md says: "React function components + hooks. Tailwind CSS. No class components."
You: "Write a component"
Claude: (hooks + Tailwind, first try)Multiply that by every task in a day and the difference is massive.
The Four-Layer Hierarchy
Claude Code reads configuration from four places, in this priority order:
Highest priority
|
1. CLAUDE.md (project root) -- team conventions, committed to git
2. CLAUDE.local.md (project root) -- your local overrides, gitignored
3. ~/.claude/CLAUDE.md (home directory) -- your personal defaults across all projects
4. .claude/rules/*.md (project) -- path-specific rules loaded on demand
|
Lowest priority| File | Scope | Git? | Example |
|---|---|---|---|
CLAUDE.md | Team | Yes | "Use pnpm. Conventional Commits. No default exports." |
CLAUDE.local.md | You | No | "My local API is on port 3001. Reply in Chinese." |
~/.claude/CLAUDE.md | You, everywhere | No | "I prefer functional style. Keep explanations short." |
.claude/rules/*.md | Team, per-path | Yes | "API files must include rate limiting." |
CLAUDE.md as a Project-Level Constitution
This layered system mirrors Anthropic's Constitutional AI research. In CAI, the model is trained with a set of principles (a "constitution") that guide its behavior. CLAUDE.md extends this concept to the project level: you are writing a local constitution that shapes how Claude behaves within your specific codebase and team conventions. The hierarchy works the same way -- project-level rules override personal defaults, just as constitutional principles override individual preferences. See Appendix A08: Constitutional AI & CLAUDE.md for a deeper exploration of this parallel.
What Makes a Good CLAUDE.md
Based on what actually works in production (not theory):
- Specific and actionable -- "Use pnpm" not "use the right package manager"
- Under 200 lines -- compliance drops when it gets too long
- Structured with headers -- Claude parses markdown structure, use it
- Include a personality/voice rule -- this is the secret weapon
The gstack project (68k stars) has a 24KB CLAUDE.md. One of the most effective sections? Voice rules:
"No em dashes. No AI vocabulary like 'delve' or 'leverage'. Sound like you're typing fast in Slack, not writing a whitepaper."
This single rule makes Claude's output dramatically more natural. We'll add something similar to our CLAUDE.md.
How the Memory System Works (Mechanically)
Claude Code has a memory system that persists information between sessions. Understanding where memories live and how they are loaded helps you debug problems and manage your configuration stack effectively.
Where memories are stored:
| Memory Type | Location | Scope |
|---|---|---|
| User memories | ~/.claude/memory/ | Apply to all your projects |
| Project memories | ~/.claude/projects/<project-hash>/memory/ | Specific to one project directory |
What triggers memory creation:
- Explicit request: You say "Remember that we use pnpm" or "Save this as a preference"
- Auto-detection of corrections: When you correct Claude ("No, always use named exports"), it recognizes this as a persistent preference and saves it automatically
- Pattern recognition: Repeated corrections on the same topic accelerate auto-save
Memory file format:
Each memory is stored as a markdown file with YAML frontmatter:
---
type: preference
name: jsdoc-requirement
description: Always add JSDoc with @param and @returns tags
created: 2026-05-15T10:30:00Z
---
The team requires JSDoc comments with @param and @returns tags on all
exported functions. This was corrected during a session working on
src/utils/validators.ts.How memory is loaded at session start:
Session starts
|
1. Load ~/.claude/CLAUDE.md (user-level rules)
2. Load CLAUDE.md (project-level rules)
3. Load CLAUDE.local.md (local overrides)
4. Load ALL memory files from ~/.claude/memory/ (user memories)
5. Load ALL memory files from ~/.claude/projects/<hash>/memory/ (project memories)
|
All of this is injected into Claude's system context BEFORE your first messageMemory priority and limits:
- Memories are additive -- they do not override each other, they accumulate
- There is a practical limit: too many memories consume context tokens, leaving less room for your actual conversation
- Stale or contradictory memories should be pruned manually (more on this in "When Things Go Wrong" below)
- Project memories take precedence over user memories when there is a conflict on the same topic
For a deeper look at how context is assembled and prioritized, see Appendix A02: Context Engineering.
Demo 3: Write a Production-Grade CLAUDE.md
Goal
Create a CLAUDE.md that would be at home in a real production project. We'll draw from patterns used by gstack, Everything Claude Code (ECC), and GSD.
Steps
1. Create the Project
mkdir -p ~/claude-demos/demo-03 && cd ~/claude-demos/demo-03
git init && npm init -y
mkdir -p src/{api,services,models} tests2. Try /init First
claude/initYou should see output like this:
$ claude
╭──────────────────────────────────────╮
│ Claude Code v1.0.23 │
│ /help for commands │
╰──────────────────────────────────────╯
> /init
I'll scan your project and create a CLAUDE.md file.
Scanning project structure...
Found: package.json, src/, tests/
Detected: Node.js project with TypeScript
Created CLAUDE.md with:
- Project overview (auto-detected)
- Directory structure
- Available scripts from package.json
✓ CLAUDE.md created (42 lines)Check what it generated:
cat CLAUDE.md/init gives you a starting point by scanning your project. But it's always generic. Let's replace it with something real.
3. Write a Real CLAUDE.md
Exit the session (Ctrl+C) and create a proper one:
cat > CLAUDE.md << 'CLAUDE_EOF'
# TaskFlow API
## Key Commands
- Install: `pnpm install`
- Dev server: `pnpm dev`
- Test: `pnpm test`
- Lint: `pnpm lint`
- Type check: `pnpm tsc --noEmit`
- Single test: `pnpm test -- --grep "test name"`
## Architecture
- `src/api/` -- REST routes and controllers (Express + Zod validation)
- `src/services/` -- Business logic, no framework dependencies
- `src/models/` -- Prisma models and database access
- `src/middleware/` -- Auth, rate limiting, error handling
- `tests/` -- Mirrors src/ structure, co-located test utils
## Tech Stack
- Runtime: Node.js 22 LTS
- Language: TypeScript 5.x (strict mode, no `any`)
- Package manager: pnpm (do NOT use npm or yarn)
- Framework: Express 5
- ORM: Prisma
- Validation: Zod
- Testing: Vitest
- Linting: Biome
## Branching & Commits
- Never push directly to `main` or `develop`
- Feature branches: `feature/<ticket-id>-<short-desc>`
- Bugfix branches: `fix/<ticket-id>-<short-desc>`
- Commits: Conventional Commits (`feat:`, `fix:`, `refactor:`, `test:`, `docs:`)
- One logical change per commit. If you need "and" in the commit message, split it.
## Code Standards
- ES modules only (import/export). Never CommonJS (require).
- `const` and `let` only. Never `var`.
- Single quotes for strings.
- 2-space indent.
- Arrow functions unless you need `this` binding.
- Every exported function needs a JSDoc comment.
- No default exports. Named exports only.
- Error messages in English (even if replies are in another language).
## API Endpoint Rules
- Every endpoint: Zod input validation + try/catch + structured error response
- Response format: `{ success: boolean, data?: T, error?: { code: string, message: string } }`
- All mutations need auth middleware
- Rate limiting on all public endpoints
## Testing Requirements
- Every service function needs unit tests
- Every API endpoint needs integration tests
- Use describe/it blocks. One assertion per `it` where practical.
- AAA pattern: Arrange, Act, Assert
- Mock external services, never mock internal modules
## Voice & Style
- Be direct. No filler phrases like "Great question!" or "I'd be happy to help!"
- No em dashes. Use -- or just break the sentence.
- No AI vocabulary: avoid "leverage", "utilize", "delve", "comprehensive", "robust"
- Sound like a senior engineer writing in Slack, not a generated document.
- When you finish a task, just say "Done." Don't write a paragraph about it.
CLAUDE_EOFNotice the Key Commands section at the top. This is inspired by Everything Claude Code (ECC, 148k stars), which puts runnable commands front and center so Claude knows exactly how to build, test, and lint your project.
The Voice & Style section at the bottom is inspired by gstack's approach. This is not just cosmetic. When Claude drops the AI-speak, the output is genuinely more useful because it matches how your team actually communicates.
4. Verify Claude Follows the Rules
Start a new session:
claudeCreate a user registration endpoint at src/api/register.ts with a corresponding service at src/services/auth-service.tsYou should see Claude request permission to create the files:
> Create a user registration endpoint at src/api/register.ts with a
corresponding service at src/services/auth-service.ts
I'll create both files following the project conventions from CLAUDE.md.
╭─ Write src/services/auth-service.ts ─────────────────────────────╮
│ (47 lines) │
╰──────────────────────────────────────────────────────────────────╯
Allow? (y/n/a):Check these:
- [ ] TypeScript with strict types (no
any)? - [ ]
import/export, notrequire? - [ ]
const/let, novar? - [ ] Single quotes?
- [ ] Zod validation on the endpoint?
- [ ] Structured error response format?
- [ ] JSDoc on exported functions?
- [ ] Named exports, no default exports?
5. Test a Conflict
In the same session:
Rewrite the imports to use require() instead of importClaude should push back. It knows the CLAUDE.md says ES modules only. If it just does what you ask without mentioning the conflict, your CLAUDE.md might not be loading properly -- check with /context.
> Rewrite the imports to use require() instead of import
I need to flag a conflict with the project conventions. The CLAUDE.md
specifies "ES modules only (import/export). Never CommonJS (require)."
If you want me to proceed anyway, I can, but this would violate the
team's coding standards. Would you like me to:
1. Keep the ES module imports (recommended)
2. Switch to require() despite the conventionWhat Just Happened?
Here is the tool-call sequence Claude used in this demo, and why it chose each tool:
Key takeaways:
- CLAUDE.md was read automatically -- you did not need to tell Claude to look at it
- Claude created the service before the route because of the dependency order
- Every code standard from CLAUDE.md was applied: named exports, JSDoc, Zod, error format
- When asked to violate a rule, Claude flagged the conflict instead of silently complying
How It Works
CLAUDE.md is loaded into Claude's context at the start of every turn. It acts as persistent instructions. When your request conflicts with a rule, Claude typically flags it rather than silently complying. This is by design -- just like a constitutional principle, the rules in CLAUDE.md shape behavior at a level deeper than individual prompts.
Demo 4: Auto-Memory in Action
Goal
Experience how Claude automatically remembers your corrections across sessions. The scenario: you're onboarding to a new team's codebase and establishing your preferences.
Steps
1. First Session: Establish Patterns
cd ~/claude-demos/demo-03
claudeStart working and drop corrections naturally:
Create a utility module at src/utils/validators.ts with email and phone validation functionsAfter Claude creates it, correct its approach:
Always add JSDoc comments with @param and @returns tags. This is a team requirement.You should see Claude acknowledge and save:
> Always add JSDoc comments with @param and @returns tags. This is a
team requirement.
Got it. I'll remember that all exported functions need JSDoc comments
with @param and @returns tags. I've saved this as a project preference.
Let me update the validators.ts file to add the missing JSDoc:
╭─ Edit src/utils/validators.ts ───────────────────────────────────╮
│ + /** Validate an email address format. │
│ + * @param email - The email string to validate │
│ + * @returns Object with valid boolean and optional error │
│ + */ │
│ export const validateEmail = ... │
╰──────────────────────────────────────────────────────────────────╯Then another correction:
For validation functions, always return { valid: boolean, error?: string } instead of throwing. We don't use exceptions for validation.Exit (Ctrl+C).
2. Check What Claude Remembered
# View the memory directory
ls ~/.claude/projects/*/memory/ 2>/dev/null || echo "Memory directory may be in a different location"
# Check the memory index
cat ~/.claude/projects/*/memory/MEMORY.md 2>/dev/nullYou should see something like:
$ ls ~/.claude/projects/*/memory/
feedback_jsdoc.md feedback_validation.md
$ cat ~/.claude/projects/a3f2b1c/memory/feedback_jsdoc.md
---
type: preference
name: jsdoc-requirement
description: Always add JSDoc with @param and @returns tags
created: 2026-05-27T14:22:00Z
---
All exported functions need JSDoc comments with @param and @returns tags.
This is a team requirement. Corrected during work on src/utils/validators.ts.You should see that Claude saved your corrections as memory files.
3. Second Session: Verify Memory Works
cd ~/claude-demos/demo-03
claudeCreate another utility at src/utils/sanitizers.ts with functions to sanitize user input (strip HTML, normalize whitespace, trim)Verify that Claude automatically:
- Added JSDoc with
@paramand@returnstags (it remembered) - Returned
{ valid: boolean, error?: string }style objects (it remembered) - Didn't throw exceptions for validation logic (it remembered)
What Just Happened?
Key takeaways:
- Memory files were loaded silently at session start -- no prompt from you
- Corrections from Session 1 were applied automatically in Session 2
- The combination of CLAUDE.md rules + memory files gave Claude a full picture of your team's expectations
- This is how Claude gets better over time within a project, without you repeating yourself
How Auto-Memory Works
Session 1:
You correct Claude: "Always add JSDoc"
Claude: saves to ~/.claude/projects/<hash>/memory/feedback_jsdoc.md
You correct Claude: "Return objects, don't throw"
Claude: saves to ~/.claude/projects/<hash>/memory/feedback_validation.md
Session 2:
Claude loads all memory files at startup
Claude automatically applies previous correctionsKey characteristics:
- Automatic -- Claude detects corrections and saves them without being asked
- Persistent -- Stored on disk, survives restarts
- Project-scoped -- Different projects get different memories
- Manageable -- You can edit or delete memory files directly, or use
/memoryin a session
Demo 5: Path-Specific Rules
Goal
Set up .claude/rules/ so that API files, frontend components, and database migrations each get their own set of standards. Rules are only loaded when Claude works on matching files, saving context space.
Steps
1. Set Up the Project Structure
mkdir -p ~/claude-demos/demo-05/{src/api,src/components,src/db/migrations,tests}
cd ~/claude-demos/demo-05
git init2. Create Path-Specific Rules
mkdir -p .claude/rules
# API endpoint rules
cat > .claude/rules/api-endpoints.md << 'EOF'
---
paths:
- "src/api/**/*.ts"
---
## API Endpoint Standards
- Every endpoint must include rate limiting middleware
- Input validation with Zod schemas (define schema in the same file)
- Wrap handler body in try/catch, return structured errors
- Response format: { success: boolean, data?: T, error?: { code: string, message: string } }
- Add request/response logging via middleware
- Include OpenAPI JSDoc annotations (@summary, @param, @returns)
EOF
# Database migration rules
cat > .claude/rules/db-migrations.md << 'EOF'
---
paths:
- "src/db/migrations/**/*.ts"
---
## Migration Standards
- Every migration MUST have a rollback plan (down function)
- Include a comment block at the top explaining what changes and why
- Never drop a column directly -- deprecate first, remove in a future migration
- Test both up() and down() before committing
- Migration filenames: YYYYMMDD_HHMMSS_description.ts
EOF
# Component rules
cat > .claude/rules/components.md << 'EOF'
---
paths:
- "src/components/**/*.tsx"
---
## Component Standards
- Function components only, no class components
- Props interface defined and exported (named `<Component>Props`)
- Include displayName for debugging
- Tailwind CSS for styling, no CSS modules or styled-components
- Co-locate component tests in __tests__/ subdirectory
EOF3. Add the Global CLAUDE.md
cat > CLAUDE.md << 'EOF'
# Project Config
## Tech Stack
- TypeScript + Node.js
- Testing: Vitest
## General Rules
- Named exports only
- File names in kebab-case
- No AI vocabulary in comments or docs
EOF4. Verify Rules Activate by Path
claudeTest API rules:
Create a user lookup endpoint at src/api/get-user.tsVerify -- Should include: rate limiting, Zod schema, try/catch, structured response, logging
> Create a user lookup endpoint at src/api/get-user.ts
I'll create this following the API endpoint standards.
╭─ Write src/api/get-user.ts ──────────────────────────────────────╮
│ import { z } from 'zod'; │
│ import { rateLimit } from '../middleware/rate-limit'; │
│ import { logger } from '../middleware/logger'; │
│ │
│ /** @summary Look up a user by ID */ │
│ const GetUserSchema = z.object({ │
│ id: z.string().uuid() │
│ }); │
│ ... │
╰──────────────────────────────────────────────────────────────────╯
Allow? (y/n/a):Test migration rules:
Create a migration at src/db/migrations/20260410_120000_add_user_roles.ts that adds a roles column to the users tableVerify -- Should include: rollback (down function), comment block, no direct column drops
Test a path without specific rules:
Create a helper at src/utils/hash.tsVerify -- Only global CLAUDE.md rules apply (named exports, kebab-case)
What Just Happened?
Key takeaways:
- Path rules are loaded conditionally based on glob patterns -- not all at once
- Each file only gets the rules relevant to its location, saving context tokens
- Global CLAUDE.md rules still apply everywhere, layered under the path-specific rules
- Files outside all path globs only see the global rules
How Path Rules Work
.claude/rules/api-endpoints.md
paths: ["src/api/**/*.ts"]
|
Only loaded when Claude works on files matching this glob
|
Files outside src/api/ don't see these rules
= no wasted context tokensThis is powerful for large projects. Your API team's conventions don't bleed into the frontend, and vice versa.
When Things Go Wrong
Even well-configured CLAUDE.md setups can produce unexpected behavior. Here are the most common issues and how to fix them.
CLAUDE.md Too Large
Symptoms: Claude ignores rules toward the end of your CLAUDE.md. Instructions at the bottom are followed less consistently than those at the top.
Why it happens: CLAUDE.md is injected into the system context. If it exceeds roughly 200 lines, later rules may get less attention due to how attention mechanisms work in large contexts. This is not a hard cutoff, but compliance degrades progressively.
Fix:
- Keep your CLAUDE.md under 200 lines
- Move detailed reference material into separate files and use
@import - Move path-specific rules into
.claude/rules/where they are loaded on demand - Put the most critical rules (key commands, deal-breaker standards) at the top
# Check your CLAUDE.md line count
wc -l CLAUDE.md
# Target: under 200 linesConflicting Rules Between Layers
Symptoms: Claude does something inconsistent -- sometimes follows one style, sometimes another. Or Claude mentions uncertainty about which convention to follow.
Why it happens: You have contradictory rules across layers. For example, your ~/.claude/CLAUDE.md says "use 4-space indent" but your project CLAUDE.md says "2-space indent".
Fix:
- Check all layers with
/contextin a session to see what is loaded - Project CLAUDE.md overrides user-level
~/.claude/CLAUDE.md - CLAUDE.local.md overrides project CLAUDE.md
- Remove contradictions rather than relying on the priority system -- explicit beats implicit
CLAUDE.md Not Being Picked Up
Symptoms: Claude ignores your rules entirely. /context does not show your CLAUDE.md content.
Common causes:
- Wrong filename: must be exactly
CLAUDE.md(all caps, no spaces) - Wrong location: must be in the project root (the directory you launched
claudefrom) - File encoding issues: use UTF-8, no BOM
- You are running
claudefrom a subdirectory instead of the project root
Fix:
# Verify the file exists where you expect
ls -la CLAUDE.md
# Verify you're in the right directory
pwd
# Check inside a session
# /contextMemory Files Getting Stale or Contradictory
Symptoms: Claude applies outdated preferences. Or Claude seems confused because two memory files give conflicting guidance (e.g., one says "use Tailwind" and another says "use CSS modules" because you changed your mind).
Fix:
# List all memory files for this project
ls ~/.claude/projects/*/memory/
# Read a specific memory to check if it's still relevant
cat ~/.claude/projects/*/memory/feedback_*.md
# Delete stale memories
rm ~/.claude/projects/<hash>/memory/feedback_old_preference.md
# Or manage interactively in a session
# /memoryBest practice: Review your memory files every few weeks, especially after major project changes (new framework, new team conventions, etc.).
Going Deeper
@import for Large Projects
CLAUDE.md supports pulling in external docs:
# CLAUDE.md
## Project Basics
...
@import docs/api-design-guide.md
@import docs/database-conventions.mdThis keeps your CLAUDE.md lean while giving Claude access to detailed reference docs when needed.
Debugging: Is Claude Reading My CLAUDE.md?
# Inside a session:
/contextThis shows everything in the current context, including CLAUDE.md and any loaded rules. If your rules aren't showing up, check the file path and glob patterns.
Memory Management
# List all memories for the current project
ls ~/.claude/projects/*/memory/
# Delete a specific memory
rm ~/.claude/projects/<hash>/memory/feedback_jsdoc.md
# In a session:
/memory # interactive memory managementProduction CLAUDE.md Patterns
The best CLAUDE.md files in the ecosystem share these patterns:
| Pattern | Example | Why It Works |
|---|---|---|
| Key Commands at top | Test: pnpm test | Claude knows how to run things without guessing |
| Voice/personality rules | "No em dashes, no filler" | Output matches team communication style |
| Negative rules | "Never use default exports" | Prevents specific known-bad patterns |
| Architecture map | "src/api/ -- routes, src/services/ -- logic" | Claude navigates the codebase accurately |
| Commit conventions | "Conventional Commits, one change per commit" | Git history stays clean |
Exercise: Build Your Own Configuration Stack
Task
Set up a complete four-layer configuration for a web project:
User-level
~/.claude/CLAUDE.md-- Your personal defaults:- Preferred response language
- Code style preferences (functional vs OOP, etc.)
- A voice rule ("keep explanations brief" or similar)
Project-level
CLAUDE.md-- Team standards:- Tech stack (pick your favorite)
- Coding standards (naming, imports, error handling)
- Key commands (build, test, lint)
Local-level
CLAUDE.local.md-- Your machine:- Local API URLs
- Personal dev preferences
- Add it to
.gitignore
Path rules
.claude/rules/-- At least 2 path-specific rule files
Then start a Claude session, have it create files in different paths, and verify that the right rules apply to each.
Success Criteria
- [ ]
~/.claude/CLAUDE.mdexists with your personal defaults - [ ] Project
CLAUDE.mdhas team-appropriate conventions - [ ]
CLAUDE.local.mdexists and is gitignored - [ ]
.claude/rules/has at least 2 rule files with different path globs - [ ] Claude's output visibly follows the right rules for each path
Hint
Set slightly different rules at different levels (e.g., different comment styles) so you can tell which level is taking effect. Check with /context if something doesn't look right.
Knowledge Check
Chapter Summary
- CLAUDE.md is the single most impactful thing you can set up. It turns Claude from a generic assistant into a team-aware collaborator.
- Four layers: project CLAUDE.md (team) > CLAUDE.local.md (you) > ~/.claude/CLAUDE.md (you, global) > .claude/rules/ (per-path)
- Voice rules work. "No em dashes, no AI vocabulary" makes Claude's output dramatically better.
- Auto-memory captures your corrections and applies them in future sessions automatically. Memories are stored locally as markdown files with YAML frontmatter.
- Path-specific rules give you surgical control without bloating the context. Rules are loaded conditionally based on glob patterns.
- Good CLAUDE.md: under 200 lines, specific, actionable, structured, with a Key Commands section at the top.
- CLAUDE.md is a project-level constitution -- it shapes Claude's behavior the same way Anthropic's Constitutional AI principles shape the base model. See Appendix A08: Constitutional AI & CLAUDE.md for the full theoretical connection.
- Context engineering matters -- how you structure CLAUDE.md, memory, and path rules determines the quality of Claude's output. See Appendix A02: Context Engineering for a deeper dive.
Next up: Chapter 3: Context Window Management. This is where most people hit a wall. Claude starts strong but gets worse as the conversation goes on. Chapter 3 explains why and shows you exactly how to fix it. GSD calls this "Context Rot" and it's real.