A session cannot determine whether its auto-memory index loaded whole, truncated, or not at all
Environment
- Claude Code CLI v2.1.220
- macOS (Darwin 25.5.0)
- Main loop: Opus 5 (
claude-opus-5[1m]). Subagents: Fable 5. - Auto-memory at
~/.claude/projects/<project>/memory/, indexMEMORY.mdplus per-fact topic files.
The request
Expose, in-session, what auto-memory actually loaded, so a partial load is detectable when it happens rather than inferred later from behavior.
Why
Auto-memory can fail in three ways that are indistinguishable from inside a session:
- The index exceeded its read limit and was truncated.
- The index was not read.
- The fact was written into a different project store.
In all three the observable result is the same: the session proceeds as though no such rule exists. It cannot flag "a rule may be missing", because it has no way to know a read was incomplete, so it states the negative with confidence.
That is what turns a capacity limit into a correctness problem. A store that fails partially and silently produces false negatives that read as ordinary answers.
Concrete instance
After a memory write, PostToolUse emitted this into the model's context:
The memory index at MEMORY.md is 161 lines, approaching the 200-line read limit. Compact it to under 140 lines now.
Two observations:
- It goes to the model, not the user. Nothing appeared in the CLI. I learned of it only because the assistant mentioned it in conversation.
- It fires only on write. A session that reads memory and never writes gets no equivalent signal, and that is most sessions.
State at time of filing: MEMORY.md at 161 lines against the 200 ceiling, indexing 131 topic files. It had been approaching the ceiling for an unknown period with nothing user-visible.
Related: write scope is not surfaced
Auto-memory is keyed to the Claude Code project directory. A fact can be written from a session whose project path differs from the tree the work runs in. The write succeeds, the index line appears, and the fact is unreachable from the sessions that need it. Nothing indicates this at write time or read time.
This asks only that the current keying be visible, not that it change.
What it cost in practice
A process ruling was saved to auto-memory the day it was given, and not also into the working tree's own operating documents. That tree resolved to a different project path, so later sessions never saw it.
Those sessions ran on hand-written prompts that restated the process from recollection. Over seven days it drifted three times, including two conventions that contradicted the written process inside the same file that referenced it. A decision ledger stopped updating while automated passes continued, and one expected output file was never created.
The project had no version control, so the drift could not be diffed. Reconstructing it took a dedicated audit pass of roughly 118k subagent tokens and produced no product output.
No individual write failed. Every component behaved as designed.
Steps to reproduce
A. No load verification
- Start a session in a project with a populated memory store.
- Attempt to determine whether the index loaded whole. There is no available check, in-session or via a CLI flag.
B. Warning is model-facing and write-only
- Grow
MEMORY.mdpast roughly 160 lines. - Trigger a memory write. The size warning arrives as a
PostToolUsenote in the model's context. Nothing surfaces in the CLI. - Start a session that only reads memory. Observe no equivalent signal.
C. Write scope is invisible
- From a session whose project path is A, record a fact concerning work that runs in tree B.
- The write succeeds with no scope indication.
- Start a session in tree B. The fact is absent, with no indication it exists elsewhere.
Proposed behavior
Any one of these would close the gap, listed cheapest first.
- A one-line load receipt in-session: index lines read, whether truncated, topic file count, resolved store path. Enough for a session to say "my memory may be incomplete" instead of asserting a negative.
- Surface truncation to the user in the CLI, on read and not only on write.
- A
claude memory statusstyle command: store path, index size against limit, whether the last session read it whole. - Show the resolved store path at write time, and flag when the fact concerns a tree outside that store.
Related issues
Listed to save triage time, not as duplicates.
- #79217 asks for the 200 line / 25KB limit to be configurable. This issue is not about the size of the limit.
- #77647 asks for the read-limit error to be documented. This is about a runtime signal rather than docs.
- #75334, #76408, #81833 report that memory is not reliably read or applied. Item 1 above would make reports of that shape diagnosable rather than anecdotal.
- #76647, #72049, #81391, #81808 concern how the store is keyed. This issue asks only that the existing keying be visible.
Current workaround
Durable state moved onto mechanisms that do not depend on the memory read succeeding: SessionStart hooks that print the operative document, version control so drift appears as a diff, and rules written into the documents the work itself opens. Memory is used as an index only.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
This is an observability boundary, not just a capacity-limit problem.
No matching memoryis only meaningful after the runtime can prove that the requested scope was loaded completely.I would keep three receipts separate:
complete | truncated | not_loadedoutcome with reason.created | updated | deduplicated | rejectedoutcome.The startup message and a machine-readable
claude memory status --jsoncommand should be two projections of the same load receipt. If the load outcome is incomplete, an explicit memory query should returnunknown/incomplete, not a confident negative.I maintain GoodMemory, which is one working reference for the retrieval/write sides of this boundary: exact scopes,
stats/inspect/ read-onlytrace, provenance, revision and forget controls, plus a local Inspector with recall traces. It does not repair or replace Claude Code's native auto-memory loader; the load receipt still belongs in Claude Code. The useful reference is the separation between canonical durable records and bounded, rebuildable context projections.That separation would also make the three failure modes in this report distinguishable without asking the model to infer loader behavior from its own recall.
Same class, from a different angle, in case the mechanism is useful here.
I arrived from the hooks side: a
UserPromptSubmithook injects a distilled brief, and it used to keep its own state saying "this layer was already delivered in this session". That state is a proxy, and it lies in three situations — truncation above 10K (#84021), compaction, and session forking on resume.What fixed it was to stop asking my own bookkeeping and start asking the transcript: every delivery appears as an
attachment.type == "hook_success"line in the session.jsonl. Scan backwards, stop at the first hit — if what comes first is the compaction summary line, the content fell out of context and has to be re-sent. Costs ~0.13s on a 17MB transcript, once per session.That is, in practice, the load receipt you ask for in item 1, except reconstructed from the outside and separately by every author. Which reinforces your argument: the information already exists in the transcript, the harness already has it, and today each of us reimplements reading it.
Would also love to see this. Memory management really is a nightmare currently.