Why Claude Code forgets your project between sessions, and what to do about it
Claude Code starts every session with an empty context window. Here is the actual mechanism behind the amnesia, and the three surfaces that fix it.
7 min readBabar Muhammad Anas
claude-codememorycontext
On this page
The short answer
Claude Code forgets your project because a session is the memory. Everything the model knows lives in one context window, and that window is discarded when the session ends. The transcript written to disk is an append-only log — nothing ever reads it back. The only knowledge that survives into the next session is whatever is loaded at startup:
CLAUDE.md, path-scoped rules files, and anything aSessionStarthook injects. Fixing the amnesia means deliberately moving knowledge out of the conversation and onto those surfaces, on a budget, because everything on them is paid for on every message.That last clause is the part most people skip, and it is why the naive fix fails.
What is actually on disk
Every Claude Code session becomes a file:
~/.claude/projects/<encoded-project-path>/<session-id>.jsonlOne JSON object per line — user messages, assistant messages, tool calls, tool results. On a long-running project these get large; a 2,787-message session is an ordinary size for a week of work, and files past 100 MB exist. The folder name is a lossy encoding of the project path (
D:\Projects\my-appbecomesD--Projects-my-app), which is why the reliable way to map a folder back to a real project is not to decode the name but to read thecwdfield that every transcript line already carries.The important property of that file is negative: nothing reads it. It is a record, not a store. Claude Code does not consult yesterday's transcript before answering today's question, and
/resumedoes not change that — it reattaches you to one specific conversation in the current directory, which is a different thing from remembering the project.So the model's knowledge of your codebase has exactly one lifetime: the session. Two events end it early.
The context window fills.
/compactsummarises the conversation to make room. It preserves the thread and drops the detail, which is precisely the material you built up over two hours of exploration. After a compact you are working with a model that knows the shape of the task and has lost the specifics.The session ends. New session, empty window, and the reasoning you did yesterday exists only as prose in a file nobody reads.
The three things that survive
Surface When it loads What it costs CLAUDE.md(project, user,.claude/, local)Every message Its full size, per turn, forever .claude/rules/*.mdwith aglobs:headerOnly when Claude touches a matching path Nothing until then SessionStart/UserPromptSubmithook outputSession start, or per prompt Whatever the hook injects That table is the whole design space. Anything you want Claude to know next Tuesday has to arrive through one of those three doors.
Why "just write a bigger CLAUDE.md" stops working
CLAUDE.mdis read on every turn. Not every session — every message. A 4,000-token file is 4,000 tokens on turn one and on turn two hundred. That is fine at 40 lines and stable conventions; a hand-writtenCLAUDE.mdis genuinely the right tool for a small project, and if yours is short and stays short you do not need anything else.It degrades with size, and it degrades in a specific way: the usual failure is not that the file is wrong, it is that the file is too big to justify and too tedious to prune. So it keeps growing, the per-turn tax keeps rising, and eventually you are paying for a paragraph about a module you deleted in March. The comparison is honest in both directions — archeus's own compare page says plainly that a 40-line
CLAUDE.mdneeds no replacement.The scalable shape is different: a small always-on index, plus detail that costs nothing until it becomes relevant.
Knowledge that lives outside the transcript
archeus's answer is a semantic memory graph stored per project at
.archeus/memory/graph.json. Modules and repos are summarised (incrementally, keyed by file hash, so unchanged code is never re-analysed) and merged with the real dependency graph extracted from the source — cross-module edges and an importance rank, not a guess from filenames.Two properties matter more than the extraction:
It is bounded. Duplicate entities merge across modules, and a global importance cap (
memory_max_entities, default 500) evicts the least-connected. The always-on cost stays flat while accuracy rises. That is the inversion that makes this work at all: memory gets leaner and sharper as the project grows, instead of heavier.Nothing shrinks without a way back. Any entity or lesson can be pinned so the cap can never evict it — pin more than the cap allows and the pins win, the cap gives way. A section of
CLAUDE.mdcan be fenced between<!-- ARCHEUS:KEEP:START -->and<!-- ARCHEUS:KEEP:END -->, and AI compression will not even send it to the model, so it cannot be reworded or dropped. Pruning names the exact entries it will remove before removing them. Every replacement is snapshotted — twelve versions ofCLAUDE.mdand of the graph, browsable with a diff and restorable, and restoring is itself snapshotted so you can walk back out again.That graph then reaches a session through the three surfaces above:
Surface What Claude sees Cost CLAUDE.md micro-index Repo one-liners, module names, a recall pointer ≤250 tokens, every session .claude/rules/archeus-mem-*.mdPer-module entities and relations, globs:-scoped0 until Claude touches those files UserPromptSubmithook (opt-in)The subgraph relevant to this prompt ≤600 tokens/prompt, under a second, local The token arithmetic behind that split is a separate topic — see how to cut Claude Code token costs for the audit and the numbers.
Retrieval, and being honest about it
The per-prompt surface only works if retrieval is good. archeus's recall engine runs four local rankers — BM25 over entity names and summaries, path and module match, dependency rank, and a confidence-weighted lesson signal — combined with Reciprocal Rank Fusion, which uses each ranker's position rather than its score, so nothing needs calibrating. No embeddings, deterministic, under half a second on 500 entities. You can call it yourself:
archeus recall "the memory graph consolidation cap"Claude can call it too, mid-session, through Bash — which is the cheapest possible form of on-demand memory, because it costs nothing until something asks.
Two lessons from building it are worth stealing regardless of the tool you use. First, a lexical ranker needs a stopword floor: without one, the query
thereturned 33 entities on this repo's own graph, and the prompt hook cheerfully injected memory into prompts that asked for none. IDF alone never reaches zero. Second, a budget should fit rather than truncate — one verbose top hit should not discard every smaller fact behind it.And the honest limit, stated in the memory docs rather than hidden: this is lexical retrieval. A query that shares no vocabulary with the stored summary will miss, and no weight tuning changes that.
Facts that stop being true
A memory that only accumulates is a memory that goes stale. When you migrate Flask to FastAPI, "this project uses Flask" is not wrong-forever, it is wrong-from-a-date. archeus invalidates superseded facts with a timestamp instead of deleting them: the old fact is kept as history and never injected again. The graph tracks what is true now and what changed, which is a materially different data model from a pile of notes.
Alongside that, entities recalled often gain weight and survive consolidation; knowledge nobody touches fades, access-based, like a forgetting curve.
Learning from finished sessions
The transcript nobody reads does contain something worth keeping: the moment you told Claude its fix was wrong, and the fix that actually worked. After each session archeus distils durable lessons — error→fix pairs, decisions, preferences — from the transcript. High-confidence ones auto-approve; the rest wait in a review screen. Approved lessons boost recall and decay if never used again.
The load-bearing detail is that this runs at
SessionEnd, notStop.Stopfires on every turn, and binding a whole-transcript scan to it means re-streaming a growing file dozens of times per session for a heuristic that only needs to run once.What to actually do
- Install it and build the graph.
pipx install archeus, open the project, pressmfor the memory hub, build. Nothing on disk moves. - Turn on the rules surface. Per-module knowledge in
.claude/rules/costs zero until Claude opens a matching file. This is the single highest-value change. - Add the recall hook only if you want per-prompt injection. It is opt-in, budgeted at ~600 tokens, and runs locally.
- Turn on recent-work memory for a token-free one-line summary per session, injected as a compact digest on the next
SessionStart— so a new session knows what the last few did. - After a compact, hand off instead of re-explaining. Hand off on the session row (or
⇧Kin the terminal UI) starts a new session seeded with the previous transcript, written to.archeus/injected-context.mdand passed as a pointer, not a paste.
- Install it and build the graph.
Where this does not help
Memory extraction is Claude calls — routed to a cheap model and run rarely, but not free. The saving is on the per-message context you stop paying for, and if your project context is already 40 lines there is nothing to save. Retrieval is lexical, so an oddly-worded prompt will miss. And none of this makes Claude Code remember on its own; it makes the next session start informed, which is the achievable version of the same thing.
Reference detail lives at docs.claudectl.space/memory.
Frequently asked
- Why does Claude Code forget everything when I start a new session?
- Because a Claude Code session is its own memory. Everything the model knows about your project lives in one context window that is discarded when the session ends, and the transcript written to disk is an append-only log that nothing ever re-reads. The only knowledge that survives into the next session is what gets loaded at startup — CLAUDE.md, path-scoped rules files, and anything a SessionStart hook injects.
- Does /compact preserve my project context?
- No. `/compact` summarises the conversation to free room in the context window, and the detail it drops is gone from that session permanently. Anything you want to survive compaction has to live outside the transcript, in a file that gets re-read — which is why project knowledge belongs in CLAUDE.md or a rules file rather than in something you explained once in chat.
- Is a bigger CLAUDE.md the answer to Claude Code's memory problem?
- Only up to a point, and it stops working exactly when you need it most. CLAUDE.md rides in the context window on every single message, so its size is a fixed tax paid per turn, and it grows monotonically as the project does. The scalable shape is a small bounded index that is always loaded plus detail that loads only when it becomes relevant.
- Where does Claude Code store session transcripts?
- Under your config directory, at `~/.claude/projects/<encoded-project-path>/<session-id>.jsonl`, one JSON object per line. The folder name is a lossy encoding of the project's real path, and every transcript line records the real working directory in its `cwd` field, which is the reliable way to map a folder back to a project.
- Can Claude Code learn from mistakes across sessions?
- Not on its own — nothing reads yesterday's transcript before today's session. It takes an external step that distils durable lessons (an error and its fix, a decision, a stated preference) out of finished transcripts and re-injects the relevant ones later. archeus does this after each session and gates low-confidence lessons behind a review screen.