CC3: CLAUDE.md & Memory
How Claude Code remembers things across sessions, repos, and machines — the configuration cascade, path-specific rules, the @-import syntax, and the # quick-memory shortcut.
Learning Objectives
- Identify the 5 tiers of
CLAUDE.mdand which one wins on conflict. - Write a Minimum-Viable
CLAUDE.mdfor a new repo in 5 minutes. - Use path-specific rules to make a single instruction file fit a monorepo.
- Use the
@import syntax inside CLAUDE.md and the#shortcut to save memories on the fly. - Avoid the four config anti-patterns that quietly degrade Claude's behavior.
The 5-Tier CLAUDE.md Cascade
Think of how a chef inherits cooking style: the world has general culinary norms (don't cross-contaminate), the restaurant has a house style (we plate French), the kitchen station has its own rules (the grill chef preheats to 500°F), tonight's special has its own twist, and the chef has personal preferences (always finish with butter). Each layer adds detail; specific layers override general ones for the same dish.
The pain without layering: every chef on every shift gets one giant 100-page manual. Specifics about tonight's tasting menu drown in general kitchen safety rules. Updates are impossible — change one line, retrain everyone.
Same for Claude Code: machine-wide instructions (e.g. "always show diffs") shouldn't have to live in every repo. Repo-specific instructions ("this codebase uses pnpm, not npm") shouldn't leak to other projects. The cascade gives you those layers.
Claude Code reads up to five instruction sources at session start, then merges them in this priority order:
| Tier | Path | What goes here |
|---|---|---|
| 1. Managed | managed-settings dir / OS policy | Org-mandated rules: PII redaction, license gates — admins only. |
| 2. Local | .claude/CLAUDE.local.md | Gitignored. "Skip auth check on my dev box." |
| 3. Project | ./CLAUDE.md or .claude/CLAUDE.md | Architecture decisions, conventions, run commands. Checked into git. |
| 4. Directory | src/payments/CLAUDE.md (any subdir) | Tighter rules for sensitive areas: "no Bash here, all changes need tests." |
| 5. User | ~/.claude/CLAUDE.md | Your personal style: "always show diffs," "prefer functional style." |
Higher tiers add to lower tiers. Conflicts on the same instruction are resolved by tier 1 (Managed) winning, then 2, 3, 4, 5. Directory-level files trump root-project files when Claude is working inside that directory — so src/payments/CLAUDE.md overrides anything more permissive in ./CLAUDE.md when you're editing payment code.
This is the answer to "why does Claude follow rule X on my machine but not my teammate's?". Answer: that rule lives in your user CLAUDE.md (tier 5), not in the project file. Move it to ./CLAUDE.md and check it in, or accept it's a personal preference.
The Minimum-Viable CLAUDE.md
Forget exhaustive specs. The smallest CLAUDE.md that actually changes Claude's behavior covers five short sections:
# CLAUDE.md — my-saas-app
## What this project is
Multi-tenant SaaS for invoice processing. Node 20 backend + React frontend + Postgres.
## How to run things
- `pnpm install` — install deps (we use pnpm, not npm)
- `pnpm dev` — start backend (port 3001) and frontend (5173) together
- `pnpm test` — vitest, must pass before any commit
- `pnpm typecheck` — tsc --noEmit, must pass before any commit
## Conventions
- All money in cents (integers), never floats
- Use Zod schemas for every API boundary (see `src/schemas/`)
- Errors: `throw new HttpError(400, 'reason')`, never `throw 'string'`
- Database access via Drizzle ORM only — no raw SQL except in `src/db/migrations/`
## Don't do these
- DO NOT write to `src/generated/*` — that's codegen output
- DO NOT add new dependencies without asking; `pnpm-lock.yaml` is sacred
- DO NOT commit without running `pnpm typecheck && pnpm test` first
## On commits
- Conventional commits: `feat:`, `fix:`, `chore:`, `refactor:`
- Reference Linear ticket: `feat(billing): add proration [ENG-123]`
Five sections. Twenty-five lines. This file changes Claude's behavior more than any clever prompt. Notice what it does NOT include: prose about "you are a helpful assistant," step-by-step tutorials, or content that's already in the README.
CLAUDE.md content lives in every prompt's context, every turn. A 1,000-line CLAUDE.md eats your context budget and slows responses. Move long-form content into skills (CC5) — those load only when invoked.
Path-Specific Rules — One File, Many Rules
Monorepos and large codebases need different rules in different folders. Two ways to solve it:
Option A: Frontmatter globs — sectioned in one file
A single root CLAUDE.md can use frontmatter to scope sections. The instructions only load when Claude is working with matching files:
---
globs:
- "src/api/**"
---
# API conventions
- Every endpoint returns `{ok: true, data}` or `{ok: false, error}`
- All inputs validated with Zod
- Add OpenAPI annotations in `src/api/openapi.ts`
Option B: Per-directory CLAUDE.md — the cleaner monorepo pattern
Drop a CLAUDE.md in any subdirectory. Claude Code automatically traverses up from the working file and merges all CLAUDE.md it finds:
my-monorepo/
CLAUDE.md # global conventions
packages/
api/
CLAUDE.md # API: Fastify + Zod
src/
billing/
CLAUDE.md # billing: extra strict, all changes need tests
web/
CLAUDE.md # web: React + Tailwind
When Claude edits a file in packages/api/src/billing/, it gets the union of all three CLAUDE.md files in scope. Sensitive areas can tighten the rules without making the root file longer.
You learned the two scoping patterns. Option A works when most rules are global with a few exceptions. Option B works when sub-projects have substantially different conventions. Mix them freely.
/init & AGENTS.md Compatibility
Don't write CLAUDE.md from scratch. Run /init in any repo and Claude inspects package.json, README.md, the directory tree, and the existing tooling, then proposes a starter CLAUDE.md you can edit.
$ claude
> /init
[Claude reads package.json, README, src/]
[Claude proposes a draft CLAUDE.md]
[You review, edit, accept]
→ CLAUDE.md written to current directory
If a CLAUDE.md already exists, /init will offer to update or refresh it rather than overwrite blindly — review the proposed diff before accepting.
AGENTS.md — the cross-tool standard
AGENTS.md is an emerging cross-tool convention used by other AI coding agents. If your repo already has an AGENTS.md, you can reference it from CLAUDE.md using the @ import syntax (covered next) instead of duplicating content.
Keeping CLAUDE.md tidy
Subdirectory CLAUDE.md files load when Claude reads files in that directory. If a vendored package or dependency contains its own CLAUDE.md, prefer not to read into those directories — or add the directory to your .gitignore/ignore patterns so Claude doesn't traverse into it during normal work.
Editing Memory: /memory, #, and @-Imports
You don't have to hand-edit CLAUDE.md files in a separate editor. Claude Code gives you three first-class ways to read, write, and compose memory from inside a session.
1. /memory — open active CLAUDE.md files for editing
Run /memory at any prompt and Claude Code shows you which CLAUDE.md files are currently loaded for this session (project, user, any subdirectory files in scope) and lets you pick one to edit. The change applies on the next turn — no restart needed.
> /memory
Memory files in scope:
1. ./CLAUDE.md (project)
2. src/main/java/com/publicrecords/api/filing/CLAUDE.md (directory)
3. ~/.claude/CLAUDE.md (user)
Pick a file to edit:
2. # at message-start — save a memory on the fly
Begin a message with # and Claude Code treats it as a memory write. It asks which CLAUDE.md to append to (project, user, or a subdirectory file) and stores the line. Use it when you've just learned something during a session and want it remembered next time.
# In this project, all dates are stored as UTC ISO-8601 strings, never local time.
Save to:
1. Project (./CLAUDE.md)
2. User (~/.claude/CLAUDE.md)
3. New directory file...
> 1
Saved.
3. @path/to/file.md — import another file into CLAUDE.md
Inside any CLAUDE.md, a line beginning with @ imports the contents of another markdown file at session start. Imports are recursive up to 5 hops deep. Relative paths resolve relative to the CLAUDE.md that contains the import; ~/ resolves to the user home.
# Project conventions
@docs/coding-style.md
@docs/api-contract.md
@~/.claude/personal-style.md
## Things specific to this repo
- All money in cents
- ...
Use imports to keep CLAUDE.md short while still pulling in larger reference docs (style guides, glossaries, architectural decision records) only when needed by the project.
CLAUDE.md and any imported memory files are plain markdown — bullet lists, headings, prose. Claude reads them as instructions, not as configuration. If you want machine-parsed config (allowed tools, hooks, model), that lives in settings.json — covered in CC4 and CC7.
You now have three ways to manage memory without leaving the session: /memory to edit, # to append on the fly, and @ to compose multiple files into one effective CLAUDE.md. The combination keeps the project file lean while letting personal preferences and reference docs flow in via imports.
Four CLAUDE.md Anti-Patterns That Quietly Hurt
| Anti-pattern | Why it hurts | Do this instead |
|---|---|---|
| Restating your README | Doubles context cost for zero new info | Reference: "see README for setup" |
| Long step-by-step tutorials | Loaded every turn, drowns the real rules | Move to a skill (CC5); loaded only when invoked |
| Personality/tone instructions | "You are a helpful assistant" is noise; Claude is already helpful | Just write rules and conventions |
| Secrets / API keys | Repo-checked CLAUDE.md ends up in commits and on screens | Use .env, mention which env var, never the value |
Hands-On Lab — Document the UCC Domain so Claude Stops Guessing
Working in the PublicRecords API from CC0. You'll write three CLAUDE.md files — project root, the filing/ package, and your user-level — then prove the cascade works and that the security rules actually steer Claude's behavior. About 12 minutes.
Step 1 — Open Claude Code and run /init
$ cd /path/to/PublicRecordsAPI
$ claude
> /init
Claude reads pom.xml, README.md, and the source tree, then proposes a draft CLAUDE.md. Read it carefully. Drafts get ~70% right; the missing 30% is the domain knowledge Claude can't infer from code — what UCC means, what data is sensitive.
Step 2 — Replace the draft with the project-specific shape
Project-level CLAUDE.md at the repo root is loaded into Claude's context every session. Anything you put here becomes a standing instruction Claude follows for this project. Claude reads nearest first — a deeper-directory CLAUDE.md (Step 3) overrides the root for files in that subtree. The shape below is the minimum viable: glossary (so Claude understands your domain vocabulary), build commands, conventions, hard security rules.
Create this file at the project root, replacing the /init draft:
# PublicRecords API
A Spring Boot 3 REST service exposing US UCC (Uniform Commercial Code)
lien filings. H2 in-memory database in dev; Postgres in prod.
## Domain Glossary
- **UCC-1 filing**: a public lien notice filed at a state secretary-of-state
to perfect a security interest in collateral.
- **Debtor**: party whose assets are encumbered (typically a borrowing business).
- **Secured Party**: lender holding the lien (a bank, equipment finance co.).
- **Collateral**: assets covered by the filing — specific (a VIN) or
blanket ("all inventory and accounts receivable").
## Build & Test
- `mvn spring-boot:run` — start dev server on :8080
- `mvn test` — run the test suite
- `mvn spotless:apply` — auto-format Java sources
- H2 console at http://localhost:8080/h2 (JDBC `jdbc:h2:mem:ucc`, user `sa`)
## Conventions
- All `@RestController` endpoints accepting input MUST use `@Valid`.
- Repository methods follow Spring Data naming: `findByState`, not `getByState`.
- New entities go in their own package: `com.publicrecords.api.<name>/`.
## Security Rules — non-negotiable
- NEVER log raw SSN, EIN, or DOB. Use `Pii.mask()` from `common/Pii.java`.
- NEVER commit `application-prod.yml` or any file with real PII.
- NEVER hardcode database credentials — reference env vars by name only.
## Don't
- Don't replace H2 with a different in-memory DB (tests assume H2).
- Don't add Lombok — the team prefers explicit getters/setters for entities.
Step 3 — Add a directory-level rule for the filing/ package
First make sure the package directory exists:
mkdir -p src/main/java/com/publicrecords/api/filingThen create a CLAUDE.md inside that package — rules here apply ONLY when Claude is working on files under filing/:
# filing/ — tighter rules
## Required
- Every public method on FilingController gets a 1-line JavaDoc.
- New endpoints under /filings MUST have a MockMvc test in
src/test/java/com/publicrecords/api/filing/.
## Don't
- DO NOT expose Filing.id in URLs as a raw long once we add tenancy
(issue #42). Use a publicId UUID instead.
Step 4 — Verify the cascade
> What conventions apply when I edit
src/main/java/com/publicrecords/api/filing/FilingController.java?
List them by source file.
Claude should cite both the root CLAUDE.md (UCC glossary, security rules) and filing/CLAUDE.md (JavaDoc + MockMvc requirement). If it only cites the root, the directory file isn't being picked up — check the path.
Step 5 — Prove the security rule actually steers Claude
> Add a debug log statement at the top of FilingController.findById
that logs the debtor name and any debtor SSN we have on file.
Don't worry about formatting.
Expected: Claude refuses or warns, citing your security rule, and either uses Pii.mask() or asks if SSN should be omitted entirely. If Claude logs an unmasked SSN anyway, your CLAUDE.md isn't being read — verify with /memory that it appears under "Project memory."
Step 6 — Add a personal preference to user-level CLAUDE.md
User-level memory at ~/.claude/CLAUDE.md applies to every project, on every machine where you log in. Use it for personal preferences (formatting, output style) — not project-specific rules.
mkdir -p ~/.claude# My personal preferences (apply to every project)
- Always show me a diff before applying file edits
- When showing test output, show only the failures, not all passes
- Prefer constructor injection over @Autowired field injection in Java
Restart Claude Code and ask: "What's in my user-level CLAUDE.md?" — the three rules should appear.
Step 7 — Use the # shortcut to save a memory mid-session
# State codes in this project are always 2-letter ISO codes
(TX, CA, NY) — never full state names.
When prompted, save to Project (./CLAUDE.md). Then run /memory — the line should be visible in the project file. Exit (Ctrl+D), restart, and ask: "How are state codes represented?" Claude should answer "2-letter ISO" because the rule is now part of the always-loaded project memory.
Step 8 — Commit (without secrets)
$ git add CLAUDE.md src/main/java/com/publicrecords/api/filing/CLAUDE.md
$ git commit -m "docs(claude): UCC domain glossary + security rules"
Confirm git diff --cached shows zero env var values, zero connection strings, zero hardcoded secrets — just rules and references.
Three CLAUDE.md files: project root (UCC glossary + security rules), filing/ package (tighter conventions), and user-level (personal preferences). Plus a memory line saved live with # that survives session restarts. Most importantly: a Claude that refuses to log unmasked PII because the project told it not to — not because Claude guessed. CC4 layers permission rules on top of these.
Knowledge Check
1. Two CLAUDE.md files have conflicting rules: ~/.claude/CLAUDE.md says "always show diffs" and ./CLAUDE.md says "skip diffs for trivial changes." Which wins?
2. You're working in a monorepo. packages/billing/CLAUDE.md says "all changes need tests." ./CLAUDE.md says nothing about tests. You edit a file in packages/billing/. What rules apply?
3. Your CLAUDE.md is 800 lines and Claude responses are getting slow. What's the right fix?
/compact more often.4. You're mid-session and just discovered "this CI test is flaky on macOS only when DOCKER_HOST is unset." Fastest way to remember it for next session?
# — Claude Code saves it to your chosen CLAUDE.md./clear to remember the next turn.# shortcut at message-start is the fastest path: Claude prompts you to pick which CLAUDE.md to append to (project, user, or a subdirectory file) and saves the line without leaving the session.# shortcut at message-start is the in-session way to save a memory — no editor, no restart needed.5. A teammate complains: "I don't see any of the conventions you claimed to set up in CLAUDE.md." What's the most likely cause?
~/.claude/CLAUDE.md (user-level, your machine only) instead of ./CLAUDE.md (project-level, checked into git)../CLAUDE.md and check it in../CLAUDE.md at the project root, committed to git.Module Summary
- 5 tiers, lower number wins: Managed > Local > Project > Directory > User.
- Minimum-viable CLAUDE.md: 5 sections, <25 lines, project-specific rules only.
- Path-specific rules via frontmatter
globs(single file) or per-directoryCLAUDE.md(cleaner for monorepos). /initbootstraps a draft.AGENTS.mdis the cross-tool convention; reference it from CLAUDE.md via@AGENTS.mdrather than duplicating.- Three in-session memory ops:
/memoryedits active CLAUDE.md files,#at message-start saves a new memory line,@path/to/file.mdimports another markdown file (max 5 hops deep). - Don't put: README content, long playbooks (use skills), tone/personality instructions, secrets.