Claude Code memory: CLAUDE.md, rules, and the notes Claude keeps on you
Most people know CLAUDE.md and stop there. The memory system has three layers, and the two you are probably ignoring are the ones that fix a bloated CLAUDE.md and explain why your instructions get followed only sometimes. Load order, path-scoped rules, auto memory, and what actually enforces anything.
Every Claude Code session starts with an empty context window, so everything the agent knows about your project on turn one came from a file. Most developers know one of those files. There are three layers, and the two people skip are the ones that solve the problems they complain about most: a CLAUDE.md that has grown too big to be followed, and instructions that get obeyed only sometimes.
Here is the whole map, with the operating advice that comes from running these files daily rather than from reading the reference once.
The three layers
- CLAUDE.md files: instructions you write, loaded into every session.
.claude/rules/: instructions you write that load only when Claude touches matching files.- Auto memory: notes Claude writes about your corrections and preferences, saved between sessions.
The first is context you always pay for. The second is context you pay for only when it is relevant. The third is written by the agent, not you, and most people have never opened it.
Layer 1: CLAUDE.md and where it lives
Four locations, listed broadest to most specific, which is also load order:
| Scope | Path | For |
|---|---|---|
| Managed policy | /Library/Application Support/ClaudeCode/CLAUDE.md (macOS), /etc/claude-code/CLAUDE.md (Linux, WSL) |
Org-wide rules, cannot be excluded by users |
| User | ~/.claude/CLAUDE.md |
You, across all projects |
| Project | ./CLAUDE.md or ./.claude/CLAUDE.md |
The team, via version control |
| Local | ./CLAUDE.local.md |
You, this project only. Gitignore it |
They concatenate, they do not override. Claude Code loads every CLAUDE.md from the filesystem root down to your working directory, so a parent directory's file appears before the one where you launched, and within a directory CLAUDE.local.md is appended after CLAUDE.md. Files in subdirectories below your working directory are different: they load on demand, when Claude reads a file in that directory. That is what makes per-package instructions work in a monorepo.
To check what actually loaded, run /context and look under Memory files. If a file is not in that list, Claude cannot see it, and no amount of rewording will help.
What earns a line
The test we use: facts go in CLAUDE.md, procedures go in a skill. "The dev bundle id is com.example.dev" is a fact the agent needs every session. "How we publish a release" is a nine-step procedure that matters twice a month, and it belongs in a skill that loads only when invoked.
Beyond that, the guidance that matches our experience:
- Target under 200 lines. Longer files consume context and, more importantly, reduce adherence. A CLAUDE.md over 4 MiB is skipped entirely.
- Be specific enough to verify. "Use 2-space indentation" beats "format code properly"; "run
npm testbefore committing" beats "test your changes". - Kill contradictions. If two files disagree, Claude may pick either one. This is the failure mode that makes people say the agent ignores instructions, and it is usually a parent directory's file arguing with the local one.
- Write the reason next to any rule that looks arbitrary. "Test against the dev bundle id" gets ignored; "debug builds used to clobber the real app's database, so always use the dev bundle id" gets followed.
- Use HTML comments for notes to humans. Block-level
<!-- -->comments are stripped before the content reaches Claude, so maintainer notes cost no tokens.
/init generates a starting file by reading your codebase, and /doctor will propose trims, cutting the parts Claude can derive from the code (directory layouts, dependency lists) and keeping the pitfalls and conventions it cannot.
Imports, and the trap in them
CLAUDE.md can pull in other files with @path/to/file, relative to the file doing the importing, recursively up to four hops. Two things to know:
Imports do not save context. Everything imported loads at launch, same as if you pasted it. Splitting a 400-line file into four imports organizes it and changes nothing about the token cost. The thing that actually reduces context is layer 2 below.
To mention a path without importing it, wrap it in backticks. Import parsing skips code spans and fenced blocks, so `@README` stays literal while @README pulls the file in.
Two practical uses. If your repo already has AGENTS.md for other agents, do not duplicate it: a CLAUDE.md containing @AGENTS.md plus any Claude-specific lines keeps one source of truth. And if you work in git worktrees, remember that a gitignored CLAUDE.local.md exists only in the worktree where you made it; import @~/.claude/my-project-instructions.md instead so your personal notes follow you across all of them. Note that a project file importing something outside the working directory triggers a one-time approval dialog, which exists because a shared repo can otherwise point at files you never reviewed.
Layer 2: .claude/rules/, the fix for a bloated CLAUDE.md
This is the underused one. Put markdown files in .claude/rules/, one topic each, discovered recursively. Rules without frontmatter load at launch like project CLAUDE.md. Rules with a paths field load only when Claude works with matching files:
---
paths:
- "src/api/**/*.ts"
---
# API rules
- Every endpoint validates input before touching the database.
- Errors use the standard error envelope, never a bare string.
That is the answer to "my CLAUDE.md is too long". Your API conventions do not need to sit in context while you edit CSS. Brace expansion works (src/**/*.{ts,tsx}), and there is a budget of 1,000 expanded patterns per rule, which you will not hit unless you write something pathological.
~/.claude/rules/ gives you the same thing at the personal level across every project, loaded before project rules so project rules win. And in a monorepo where other teams' files keep getting picked up, claudeMdExcludes in .claude/settings.local.json skips them by glob.
Layer 3: auto memory, the notes Claude keeps
Auto memory is on by default, and it is the layer most people have never looked at. As you work, Claude saves four kinds of note for itself:
user: your role, expertise, working preferencesfeedback: corrections you gave, and approaches you confirmedproject: ongoing work and decisions it cannot derive from code or git historyreference: where to find things outside the repo, like a dashboard or tracker
It deliberately skips anything derivable from the codebase and anything your CLAUDE.md already says. Storage is ~/.claude/projects/<project>/memory/, keyed off the git repository, so every worktree of a repo shares one memory directory. It is machine-local and never synced.
The mechanics that matter:
MEMORY.mdis an index, and only the first 200 lines or 25KB load at session start. Topic files next to it are read on demand.- It is not loaded into subagents. A subagent starts cold unless it is a fork of the parent conversation, or unless it maintains its own memory directory.
- It is plain markdown you can edit or delete. Run
/memoryto browse, toggle it off, or open the folder.
Worth doing once: open that folder and read what has accumulated. In ours, the entries that earn their place are the ones no code could have told the agent, like a pricing change and a correction about verifying claims before publishing them. The entries that age badly are the ones naming a specific file or flag, because those get stale silently. When you audit, delete anything that reads like documentation and keep anything that reads like a lesson.
If you want something remembered right now, say so in the session ("remember that the API tests need a local Redis"); it lands in auto memory. If it belongs in the shared, committed rules instead, say "add this to CLAUDE.md" explicitly, because those are different destinations with different audiences.
The part that explains the frustration
CLAUDE.md is context, not enforcement. It arrives as a user message after the system prompt, and Claude tries to follow it with no guarantee of strict compliance. That single fact explains most "why does it ignore my rules" complaints, and it points at the fix:
- For guidance, keep using CLAUDE.md, and make it specific and non-contradictory.
- For something that must happen at a fixed moment, use a hook. A
PreToolUsehook that blocks a command runs regardless of what the model decides. This is the whole reason our gate-not-hope rule exists. - For system-prompt level instructions in automation,
--append-system-prompton every invocation.
Related, and reassuring: project-root CLAUDE.md survives /compact, because it gets re-read from disk and re-injected. If an instruction vanished after compaction, it was given only in conversation, which is the argument for writing it down in the first place.
Debugging checklist
/contextand read Memory files. Not listed, not loaded./memoryto open any of them, including files that do not exist yet.- Look for a parent directory's CLAUDE.md contradicting your local one, and for a stale auto memory saying the opposite of a fresh rule.
- Over 200 lines? Move path-specific content into
.claude/rules/withpaths, procedures into skills, and delete anything derivable from the code. - Still not obeyed, and it must be? It is a hook, not a memory file.
- For deep debugging, the
InstructionsLoadedhook logs exactly which instruction files loaded and when.
Our setup, for what it is worth
The user-level ~/.claude/CLAUDE.md holds one hard rule (never implement without explicit approval) and one standing exception for a toggle we run constantly. The project CLAUDE.md holds the things no one could derive: which build produces the dev instance and why testing with the real one corrupted real data, the pipeline's routing table, and the deploy rule. Procedures live in skills. Auto memory holds corrections we gave once and would rather not give again.
The result is boring, which is the point. Between them, a fresh session knows what a new teammate would need on day one, and nothing else.
Worth noticing, though: the agent now has a system for accumulating what it learns across sessions, and you probably do not. The stack you are working in changes monthly, and the things you look up for the fifth time are still going into a browser tab instead of anywhere durable. That asymmetry is why we build Unwait, which puts one flashcard in the gap while the agent works. The agent takes notes on you; it seems fair to take some back.