Project memory has one loading tier, so a well-maintained CLAUDE.md becomes the main cause of compaction

Status Open
Maintainer reply None cached
Activity 1 comment · opened Sep 30, 2026

*Filed by a Claude Code user. The analysis and measurements below were produced by Claude
during a working session on my own project, at my request, and I'm passing them on because the
conclusion looks generally applicable rather than specific to my repo. First person below is
Claude's.*

---

Feedback from a long-running single-developer project (a SwiftUI weather-station app, six
Xcode targets, plus a Python alerts server). Its CLAUDE.md is unusually well maintained —
every hard-won finding is written down at length with the rejected alternatives attached,
which is exactly what the docs encourage. That discipline paid for itself many times over.
It also, eventually, became the single largest consumer of the context window.

The problem

CLAUDE.md is injected in full at the start of every context window, including immediately
after each compaction
. Nothing in the format distinguishes

  • this must be in context before I write a line — I get no prompt to go looking, and the

line I'm about to write looks completely ordinary; from

  • this is only needed once a symptom is visible — the symptom itself will send me searching.

Because there is no such distinction, a well-maintained project memory grows monotonically.
Every finding is a permanent addition to the per-window tax, and there is no natural point at
which anything leaves. Past a certain size the memory file is itself the dominant cause of the
compaction it then has to survive.

Measured, on this project

CLAUDE.md   481,186 bytes · 74,745 words · 6,740 lines   ≈ 120,000 tokens
growth      413,250 → 481,186 bytes in four days         ≈ +17,000 tokens

Section breakdown: a 115-entry "gotchas" list is 257 KB (54%), a status/design section 97 KB
(20%), four server phases 67 KB (14%).

The finding that makes this tractable: size and risk are inversely correlated.

40 of 120 entries (a third)   = 67% of the text
smallest 60 entries (half)    = 17% of the text

The entries that must be resident — "App Groups do not cross devices", "narrowing a widget's
options deletes the widgets already placed", "reading an unset optional intent parameter
crashes" — are 150 to 1,500 bytes each and sit at the bottom of the size list. The entries that
dominate the bytes are 4,000–9,000-byte investigation writeups: how to drive the simulator, why
a desktop widget renders blank, why a changed app icon doesn't appear. Those only ever matter
once something is visibly wrong, and the symptom is a perfectly good index.

So the file can be cut by roughly two thirds without moving a single entry that guards a
write
. On this project: 257 KB of gotchas → 84 KB, a 68% reduction.

The proposal

Treat CLAUDE.md as a cache, and give it the three things a cache needs.

1. An admission policy based on when the entry fires, not on how important it is.
Everything in a file like this is important — that is why it was written down. The useful test
is whether a one-line summary can change behaviour as reliably as the full text. For a
write-time landmine it cannot. For a symptom-time recipe or a settled "do not reopen this
decision", it can. The rest moves to linked files with a one-line index entry naming the
symptom, not the explanation.

2. A rule that makes a misclassification cheap instead of silent. This is the load-bearing
part, and it came from the developer rather than from me:

At the first sign of difficulty, grep the linked files before forming a theory — before the first hypothesis, not after two failed ones.

Misclassification is otherwise invisible: file something as symptom-time when it was actually
write-time and nothing tells you, the mistake just gets made. With the rule, the worst case is
one extra grep.

3. Instrumentation of the misses only. Put a hit tally on each index line and bump it when
the full text has to be opened — one character, in a file already open, at the moment the miss
happens. Three hits and the entry is promoted back to resident. Reading something already in
context leaves no trace, so hits are unmeasurable; that turns out to be fine, because a miss is
the only event that should ever change what's cached.

A free signal worth knowing about. Grepping the codebase for citations of each entry
("see gotcha 41") separates the self-indexing findings from the rest. On this project 84 of 120
were cited from source, and 36 of the 53 move candidates were — the code already points at
them, so moving the text costs nothing. The 17 uncited move candidates turned out to share a
property: they are all operational rather than code-level — install procedures, OS-level
caches, toolchain behaviour, signing. Nothing cites them because they are not about a line of
source, which is exactly why they need the rule in (2).

One caveat that stops this signal being over-read: a citation protects the file that already
learned the lesson, not the next file that hasn't. It is good evidence for relocating a symptom
recipe and weak evidence for relocating a landmine.

What would help from the product side

  • A native tier marker in CLAUDE.md — a section annotation meaning "keep the index

resident, fetch the body on demand". Today the split has to be done by hand across separate
files, and the reference from index to body is a convention the model has to honour rather
than something the harness enforces.

  • Make the memory file's token cost prominent, at session start and after each compaction.

Growth of this kind is invisible until it hurts: the file was adding roughly 4,000 tokens a
day here and nobody noticed until compaction frequency changed. A single line — "project
memory: 120k tokens, 60% of window" — would have prompted this months earlier.

  • Compaction re-injects the memory file whole. A tiered format would let the resident part

survive a compaction while the bodies stay out, which is where most of the benefit is.

  • The miss counter could be free. If the harness noted which linked docs a session actually

opened, eviction data would accumulate with no user effort and no bookkeeping in the file.

Why this isn't just "write a shorter CLAUDE.md"

The advice to keep project memory concise is already in the docs, and following it here would
have been the wrong call: the length is what makes the file work. Several entries record
multi-day investigations whose value is precisely in the detail — which four hypotheses were
eliminated, which log query settles it in one line, which earlier version of the note was
wrong and why. Compressing those would destroy the thing that stops them being re-derived.

The problem is not that the file is too long. It is that a format with one loading tier forces
a choice between "always in context" and "not written down", when most of the content wants a
third option.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗