A memory is a claim about the past that gets read as a claim about the present.
That is the whole problem with Claude Code memory, and it is why the advice that circulates about it, which is mostly about how to save more, points the wrong way. Saving is the easy half. Claude saves things on its own now. The half that decides whether memory helps you or quietly misleads you is what you refuse to save, and how much you trust what is already there.
This is the memory layer of the harness argument: context that is not in the code has to live somewhere, and the failure is specific. Memories go stale and get trusted as current, or they store what the repo already records. First, what the product actually does, because most of what gets written about it skips the mechanism.
Two mechanisms, and Claude treats both as context
Anthropic’s memory documentation opens with the fact the rest of it depends on: each session begins with a fresh context window, and two things carry knowledge across sessions.
| CLAUDE.md files | Auto memory | |
|---|---|---|
| Who writes it | You | Claude |
| What it holds | Instructions and rules | Learnings and patterns |
| Scope | Managed policy, user, project, local | One directory per repository |
| What loads | Whole files at launch, subdirectory files on demand | First 200 lines or 25KB of MEMORY.md; topic files on demand |
The CLAUDE.md side has four locations. A managed policy file set by an organization, ~/.claude/CLAUDE.md for you across every project, ./CLAUDE.md or ./.claude/CLAUDE.md for the project, and ./CLAUDE.local.md for personal notes you keep out of version control. Files in the directory above your working directory load at launch, and they are concatenated rather than overriding each other: content runs from the filesystem root down, so the instructions closest to where you started are read last. A CLAUDE.md in a subdirectory loads later, when Claude reads or edits a file there. Files can pull in other files with @path/to/file, up to four hops deep, and the docs are blunt that imports organize a long file without making it cheaper, because imported files load at launch too.
Auto memory is the newer half. It is on by default in local sessions, and the docs describe four kinds of note that Claude writes for itself, recorded as a type field in each file’s frontmatter: user, feedback, project and reference. Each memory is a file in ~/.claude/projects/<project>/memory/. A MEMORY.md index sits beside them, and the docs say it holds one line per memory. The directory is derived from the git repository, so all worktrees of one repo share it, and it is machine-local: nothing syncs it to another computer.
The page has a few sentences that matter more than the rest. The first: “Claude treats them as context, not enforced configuration.” The second, in the troubleshooting section: CLAUDE.md content is delivered as a user message after the system prompt, and “there’s no guarantee of strict compliance.” Anything that has to hold every time belongs in a hook. Anything that is a multi-step procedure belongs in a skill, which the skills versus MCP versus CLAUDE.md post covers. What is left for memory is the narrow thing neither can hold: facts about you, your preferences and your project’s reasons.
My setup is not a different system. It is the documented auto memory layout with house rules on top, one fact per file, a type in the frontmatter, and an index kept short. The rules are mine and the mechanism is Anthropic’s.
The four types, and how each one goes stale
The type field is not decoration. It tells you how long to trust the entry.
| Type | What it stores | How it goes stale |
|---|---|---|
user |
Your role, expertise, working preferences | Slowly, then all at once when the role or the stack changes. Wrong entries shape tone and assumptions on every task, so a stale one is hard to spot |
feedback |
Corrections you gave, and approaches you confirmed | When the tool, model or workflow underneath changes. The rule outlives the reason for it. Confirmations go stale more quietly than corrections |
project |
Ongoing work, deadlines, decisions the code cannot show | Fastest. It is a snapshot of a situation, and the situation moves without telling the memory |
reference |
Where information lives outside the repo: a tracker, a dashboard | When the system moves, is renamed or is retired. The pointer still looks fine |
Feedback entries carry the most future value per line, and I give them and project entries two extra lines: a “Why:” line and a “How to apply:” line. The why is what lets a future session judge an edge case instead of following the rule off a cliff. It is also what tells you when a rule has expired, because you can see whether the reason still holds.
Project memories are the dangerous ones. A project memory says “this is true now,” and now keeps moving. Docs and a good CLAUDE.md can describe a stable codebase. A project memory describes a situation, and situations are exactly what change without anyone editing the file.
What not to save
The docs say Claude skips anything it can derive from the codebase, such as architecture, file paths or debugging fixes, and anything your CLAUDE.md already says. That default is right. Here is the fuller list I hold myself to, with the reason on each line, because a rule with no reason gets talked past.
- Code structure, file paths, conventions. The repo records them, and the repo is always current. A memory of the layout is a second copy that is wrong the moment someone moves a file.
- Git history and who changed what.
git logandgit blameare authoritative. A memory of recent changes is a worse log. - Debugging fixes and recipes. The fix is in the code and the reason is in the commit message. A memory of it duplicates both and ages faster.
- Anything already in CLAUDE.md. Two copies drift. The docs warn that when two instructions contradict each other, Claude may pick one arbitrarily, and a memory that disagrees with your CLAUDE.md creates exactly that.
- Task state. In-progress work and the current conversation expire within the session. Writing them down just leaves an entry that is false by tomorrow.
- Personal identifiers that showed up in ambient context. Emails, handles, addresses that appeared in config files or account metadata without being offered. Collecting them into memory is surveillance of yourself, and it puts them in a file that gets loaded into every session.
One more rule, which covers the case where you ask. If I tell Claude to remember a pull request list or an activity summary, the right response is to ask what was surprising about it. The list is derivable. The surprise is not, and the surprise is what deserves a line.
What survives all of that is short: who I am to this tool, how I want to be worked with, the reasons behind decisions the code cannot show, and the location of things that live outside the repo.
A memory is a claim about when it was written
A single rule in my project instructions does more work than the rest of the memory setup combined:
A memory that names a specific function, file, or flag is a claim that it existed when written. It may have been renamed, removed, or never merged.
Before recommending anything from a memory, I make Claude check. If the memory names a file path, confirm the file exists. If it names a function or flag, grep for it. If I am about to act on the recommendation, verify first. “The memory says X exists” is not the same as “X exists now.” And a memory that summarizes repo state is frozen at the moment it was written, so for recent or current state the right sources are git log or the code itself.
memory says: "X lives in file F"
│
▼
┌──────────────────────────┐
│ does F exist? grep for X │
└─────┬──────────────┬─────┘
│ yes │ no
▼ ▼
act on it correct or delete
the memory first
a memory describing current state
is checked against git log or the
code, never trusted on its ownThe docs half-support this. When Claude writes a memory file that has frontmatter, Claude Code records a modified timestamp in it, so both you and Claude can see how current the fact is. That requires v2.1.214 or later. A timestamp tells you how old an entry is, and nothing tells you whether it is still true. Age is a prompt to check, not a verdict.
Three receipts
These are all from my own setup, all memory files for this site’s workflow, and each shows a different shape of the problem.
First, a memory that became false. The deploy memory for this site said there was no in-repo deploy configuration and no .github/workflows directory. Both facts were true when written. Then a scheduled-publish workflow was added, and future-dated posts stopped going live when they merged. The memory had to be corrected on 2026-08-23, and the correction mattered more than the original fact. The old entry was not just stale. It was confidently wrong in the exact direction that would have had me waiting for a merge to publish something that now needed a separate release step.
Second, a memory that was true and still told me nothing. On 2026-10-08 the publishing memory still said the post queue “runs through 2026-09-16.” Nothing was wrong with the sentence. The trouble was that nobody had noticed nothing had been published since that date. A memory can be accurate and still sit there reassuring you about a situation that has changed around it. Its date was the clue, and only because I looked at the date.
Third, a memory that saved a wasted call. An SEO memory written on 2026-08-23 recorded that submitting a sitemap to Google through the API was blocked, and it said not to re-attempt the call expecting a different result. On 2026-10-08 that was still true. This is the kind of memory worth having: a negative result with a date, which spares the next session a call that was always going to fail. It earned its place because it recorded something the code cannot show, and it carried the date I needed to judge it.
The pattern across all three: the first memory was an assertion about state, the second a deadline with no checkpoint, and the third a recorded dead end. The first two rotted. The third held up because it was a dated observation instead of a standing fact.
Memory needs maintenance, and nobody does it by default
The honest concession is that none of this runs itself.
What the product does automatically is real but narrow. When MEMORY.md nears the 200-line or 25KB limit, Claude Code reminds Claude to keep one line per entry, move detail into topic files, and merge or drop stale entries. If the file goes over a limit, the write still succeeds, but Claude Code returns an error telling Claude to rewrite the index, because everything past the limit is dropped on the next load. That keeps the index small. It does not make the entries true. It is driven by size, and a stale entry takes as many lines as a correct one.
For the instruction files there is more. Running /doctor prompt-audit has Claude look through your CLAUDE.md, CLAUDE.local.md and AGENTS.md files, plus rules, skills and subagents, for outdated or conflicting content, including references to files or commands that do not exist. It produces a report and changes nothing until you say so, and it needs v2.1.283 or later. The default scope the docs list does not include the auto memory directory. The command does accept a path to a single file or directory, so the memory directory can be audited on request, but nothing audits it unless you ask.
So the maintenance is mine. I run a periodic consolidation pass by hand with four steps: list what exists, gather what changed, merge new signal into existing files and delete what has been contradicted, then prune the index. The first two receipts are cases where the pass had not run recently enough. Memory is not something you set and forget.
There is a fair case that this cost is not worth paying. A practitioner on X, @EXM7777, posted on 2026-10-05 after what they described as more than 70 hours in Claude Code: “only use a global claude.md, let the code and your knowledge base do the rest.” For a lot of work, that is right, because most of what people stuff into memory is derivable from the repo, and a lot of the advice on saving more is the thing that creates the staleness. The case for keeping memory anyway is narrow and specific: the reasons behind decisions, the way you want to be worked with, and the dead ends that cost a call to discover. If an entry does not fit one of those, it probably does not belong.
What to do with this
Start with the CLAUDE.md side, since it is cheaper to get right. Keep each file under the 200-line target the docs set, make every instruction concrete enough to check, and move anything that must happen every time into a hook. Run /context and read the Memory files list to confirm what actually loaded.
Then open the auto memory directory with /memory and read what Claude has saved. It is plain markdown you can edit or delete. For each entry, ask the questions in this post: is it derivable from the repo, is it already in CLAUDE.md, does it name a file or function I can still find, and when was it written. Delete what fails. The memory worth keeping is small, dated and about something the code cannot say.
Delegation adds a trap. A subagent starts without the main conversation, forks aside, as the subagents post explains. If a delegated task depends on a fact you keep in memory, put the fact in the briefing instead of assuming the subagent will find it.