Research
Claude Code Memory vs CLAUDE.md: What Each Costs and Which One Wins
· 8 min read
On this page
Two questions about Claude Code’s auto memory keep getting asked and never answered. On Facebook: “Does turning on memory feature eat into token credits quicker?” On Reddit, a thread titled “Lets talk about all the differnet .md memory files” where someone complains that Claude Code “is getting really pushy about wanting to use memory files.” Both threads fill up with guesses. The official docs page has a section called “CLAUDE.md vs auto memory” that explains the split cleanly and never mentions a number.
So I measured it. Auto memory costs me 664 tokens at startup, and every one of those tokens comes from MEMORY.md. The files it indexes — the ones holding the actual facts — cost zero. Not “a little.” Zero, confirmed by two fixtures that differ by 11KB of memory files and produce byte-identical token counts.
The two systems, briefly
CLAUDE.md you write by hand. Auto memory Claude writes for itself: a directory at ~/.claude/projects/<path-slug>/memory/ holding one fact per file, plus a MEMORY.md index that points at them. The path slug is your working directory with every / turned into -, which is what makes this measurable — you can create a fixture directory, create its matching memory directory, and control both sides exactly.
The docs say both are “loaded at the start of every conversation.” That sentence is doing a lot of work, and it turns out to be true of one file and false of everything it points to.
The canary says the index loads and nothing else does
Asking a model what’s in its context is worthless — it will guess something agreeable. So plant facts that exist nowhere else and see which ones come back, with every file-touching tool disabled so it can’t cheat. This is the same method I used to establish that Claude Code never reads AGENTS.md.
Three separate keys, one per location. PROJECT_CODENAME is FALCON in CLAUDE.md, MEMORY_KEY is ORCHID in MEMORY.md, FILE_KEY is TOUCAN inside an indexed memory file:
claude -p 'Using no tools and reading no files, answer on one line in exactly this form:
"PROJECT_CODENAME=<value> MEMORY_KEY=<value> FILE_KEY=<value>"
Each value is a single word that is already in your context. Use UNKNOWN for any
value you do not already have.' \
--model sonnet --output-format json \
--disallowedTools Bash Read Grep Glob Edit Write WebFetch WebSearch Task TodoWrite NotebookEdit
Two rounds, Claude Code 2.1.251, every case identical both times:
| Fixture | What’s on disk | PROJECT | MEMORY | FILE |
|---|---|---|---|---|
| Empty | nothing | UNKNOWN | UNKNOWN | UNKNOWN |
| Index only | canary in MEMORY.md | UNKNOWN | ORCHID | UNKNOWN |
| File only | canary in an indexed file | UNKNOWN | — | UNKNOWN |
| Both systems | CLAUDE.md + index + file | FALCON | ORCHID | UNKNOWN |
| Memory only | index + file, no CLAUDE.md | UNKNOWN | ORCHID | UNKNOWN |
FILE_KEY is UNKNOWN in every row where it exists on disk. The memory file is indexed, its one-line description is visible, and its contents are not in the context. In the file-only fixture the model answered MEMORY_KEY=deploy-target — it was reading the index entry’s link text and guessing, which is exactly what you’d expect from something that can see a table of contents and not the chapters.
The last row matters separately: memory loads with no CLAUDE.md present at all. The two systems are independent, not a fallback chain.
The price, and the two comparisons that settle it
Now the difference method — same approach as where my 39,810 startup tokens go. Run claude -p 'hi' in a fixture, read input_tokens + cache_creation_input_tokens + cache_read_input_tokens off the first usage object, change one thing, run again.
Sonnet, two rounds each. Every single fixture returned the same number both times — zero measurement noise, which is a nice change from the ±1,000-token cache swings I hit when I tried this on AGENTS.md.
| Fixture | MEMORY.md | Memory files | Their total size | Prefill | Delta |
|---|---|---|---|---|---|
| Empty | — | 0 | 0B | 26,465 | baseline |
| One file | 65B | 1 | 113B | 26,605 | +140 |
| Twenty orphans | 68B | 20 | 9,515B | 26,619 | +154 |
| One big file | 68B | 1 | 11,374B | 26,619 | +154 |
| Index, no files | 87B | 0 | 0B | 26,622 | +157 |
| Twenty indexed | 1,393B | 20 | 9,515B | 27,126 | +661 |
| Index of twenty ghosts | 1,393B | 0 | 0B | 27,129 | +664 |
Two comparisons carry the whole argument.
Rows three and four. Same 68-byte index. One holds twenty memory files totalling 9,515 bytes; the other holds a single 11,374-byte file. Prefill is identical to the token: 26,619 both times. Nearly 2KB of difference in what’s on disk, zero difference in what gets loaded.
Rows six and seven. Same 1,393-byte index listing twenty entries. One has all twenty files on disk; the other has none — the index points at twenty files that do not exist. 27,126 versus 27,129. The version with no files at all measured three tokens higher. Claude Code doesn’t check whether the things in your index are real, because it never opens them.
Fit a line through the index sizes and you get roughly 2.6 bytes per token with a fixed cost of about 128 tokens for having memory at all. Which gives you a rule you can apply without running anything: your auto memory costs what your MEMORY.md costs, and nothing else. Mine is 278 bytes for this blog’s repo, so it’s buying about 235 tokens of my startup context. XDA measured their /context dropping when they disabled auto-memory; this is what the recovered tokens were made of.
Recall doubles your input tokens
Zero at startup isn’t zero overall — it moves the cost to the moment something actually needs a fact. Same question, three conditions, tools enabled this time:
| Where the answer lives | Turns | Cumulative input tokens | Answer |
|---|---|---|---|
Written directly in MEMORY.md | 1 | 36,371 / 36,455 / 36,371 | TOUCAN |
| In an indexed memory file | 2 | 72,991 / 73,694 / 73,694 | TOUCAN |
| Nowhere | 1, 1, 2 | 36,287 / 36,287 / 72,814 | can’t answer |
A recall costs one extra turn, and an extra turn doubles the input. Not “adds the file” — doubles, because the second request resends the entire conversation so far along with the tool result. 36K becomes 73K to retrieve a single word.
Dollar figures from the same runs swung between $0.0074 and $0.075 for identical conditions depending on how the prompt cache landed, so I won’t quote a price. The token counts were stable to three digits; the billing wasn’t.
There’s a failure mode hiding in the disabled-tools column too. Asked for a fact that lives in an indexed file it couldn’t open, the model burned five to six turns and 169K–190K cumulative input tokens before giving up. An index it can see pointing at contents it can’t reach is worse than no index — it goes looking, repeatedly.
When the two systems disagree, CLAUDE.md wins
The Reddit complaint about Claude Code being “pushy” about memory files raises a real question: if CLAUDE.md says one thing and memory says another, which one governs?
I put a direct instruction in both — answer the build word with ALPHA — and the opposite word in the other. Then I swapped the assignment, so that any winner has to win from both sides or it’s just word-order bias.
| Fixture | CLAUDE.md says | Memory says | Answered cleanly | Winner |
|---|---|---|---|---|
| A | ALPHA | BETA | 4 of 7 | ALPHA, 4/4 |
| B | BETA | ALPHA | 7 of 7 | BETA, 7/7 |
Eleven for eleven to CLAUDE.md. The memory value never came out once, in either direction. That’s not an accident of ranking — it’s the design. Recalled memories arrive wrapped in a system-reminder block that explicitly frames them as background context rather than instructions, so an imperative sitting in a memory file is read as a note about the past, not an order.
The asymmetry in the middle column is the part I can’t fully explain. Fixture A spent three of its seven runs not answering at all — it noticed the contradiction and went off trying to edit the memory file to fix it, then reported that no write tool was available. Fixture B did that zero times. My best guess is alphabetical: ALPHA in the memory file may read as more assertive against BETA than the reverse. I found alphabetical order deciding a different contest when I measured which skill descriptions actually fire, so I’m not ready to call it noise, but seven rounds isn’t enough to call it anything else either.
What to do with this
Keep MEMORY.md short, and stop worrying about the files. The index is the only thing you pay for, on every session, forever. The files behind it are free until read. A memory directory with sixty facts in it costs you sixty index lines — so the discipline that matters is one line per fact, not one file per fact.
Put instructions in CLAUDE.md, not memory. Not because memory loses a fight, but because it never enters one. If you want a rule enforced, it belongs in the file that’s read as instructions. Memory is for facts you want recalled, which is a different job.
Don’t index what you won’t recall. Every stale entry is startup tokens on every future session plus a chance the model goes hunting for a file that no longer says anything useful. Delete rather than accumulate.
Re-measure after upgrades. All of this is Claude Code 2.1.251 on 2026-08-31. The MCP context tax I measured in my startup token breakdown had already been quietly re-engineered by the time I wrote the follow-up. Agent internals expire fast; the fixtures above take about ten minutes to rebuild, and the canary question is one claude -p call.