DEV Community

Zac
Zac

Posted on

Stop Re-Explaining Your Codebase to Claude Code Every Session

Claude Code doesn't remember anything between sessions. You already know this. What you might not have measured is how much time you actually lose to it.

Developers who tracked their Claude Code sessions found 59+ documented compactions in a single project. Each one wiped working knowledge mid-session. Each session start required the same re-orientation: re-explain the architecture, re-establish the naming conventions, re-litigate decisions that were already settled two sessions ago.

There's a GitHub issue titled "Context compaction loses critical working knowledge mid-session" that captures exactly this. It's not a bug Anthropic can easily fix. Context windows are finite. Compaction is necessary. The problem is that there's no built-in mechanism to persist what matters across that boundary.

So you end up doing it manually, ad hoc, inconsistently.

Why CLAUDE.md isn't enough

The first thing most people try is stuffing everything into CLAUDE.md. Project overview, tech stack, conventions, decisions, current task. It grows. Eventually it's 400 lines and Claude is loading the entire thing at the start of every session whether it's relevant or not.

CLAUDE.md is the right place for static project instructions. It's the wrong place for working memory. Those are different things.

Static: "We use Prisma for all database access."
Working: "We decided yesterday to skip soft deletes on the orders table because of query complexity."

Mixing them means your static instructions get buried in session-specific noise, and your session-specific knowledge gets lost the moment you edit CLAUDE.md for something unrelated.

The 3-tier memory system

The fix is a file system that mirrors how working memory actually functions: a fast-access layer for current state, a reference layer for detailed history, and your existing codebase as ground truth.

L1: MEMORY.md (always loaded)

This is the one file Claude reads at the start of every session. It has one job: answer "where am I and what am I doing right now?"

Keep it under 120 lines. If it's longer, every session pays a load cost for context that probably isn't relevant to today's work.

# [Project Name] Memory
> Last saved: 2026-03-17 14:30

## Current State
Implementing Stripe webhook handler. Auth module complete.
Next: write webhook signature verification.

## Active Task
- Goal: payment integration
- Status: in progress
- Next step: api/webhooks.ts — signature check function
- Blocked on: nothing

## Critical Rules
- Always use Prisma for DB access, never raw SQL
- API responses must match /docs/api.yaml spec
- Never commit directly to main

## Recent Decisions
- 2026-03-16: Skipped soft deletes on orders — query complexity
- 2026-03-15: Chose Stripe over Paddle — existing team familiarity

## Pointers to L2 Memory
- Architectural decisions: memory/decisions.md
- Codebase patterns: memory/patterns.md
- Task progress: memory/progress.md
Enter fullscreen mode Exit fullscreen mode

L1 is always loaded. Keep it high-signal.

L2: memory/*.md (loaded on demand)

These are the reference files Claude reads when it needs depth on a specific topic. Not every session. Only when relevant.

memory/decisions.md    — full architectural decision history
memory/patterns.md     — codebase patterns and conventions
memory/progress.md     — multi-session task tracking
Enter fullscreen mode Exit fullscreen mode

Working on auth? Claude reads memory/decisions.md to see what was decided about the auth approach. Continuing a long task? It reads memory/progress.md to find the exact checkpoint.

The key constraint: L2 files are only useful if L1 has pointers to them. The Pointers section in MEMORY.md is what makes this work. Without it, Claude won't know these files exist.

L3: your existing codebase

L3 is everything else. README, source files, PR descriptions, git history. Claude can read these during work. You don't replicate their contents in L1 or L2. You just reference them.

"See api/webhooks.ts for the implementation" is enough. Don't copy the implementation into a memory file.

Information flows down the tiers, not up. L1 points to L2. L2 points to L3. Nothing gets duplicated.

The 5 commands

The memory system works through five commands that you run at predictable points in your workflow.

/memory-load — Run this at the start of every session. Claude reads MEMORY.md, extracts your current task and critical rules, then loads whichever L2 files are relevant to the active work. It tells you exactly what it knows: current task, status, next step, key rules.

/memory-save — Run this before compaction, when context is getting long, or at the end of a session. Claude extracts what it learned: decisions made, patterns discovered, progress, failures. It writes a structured summary to MEMORY.md and appends details to the L2 files. The result: "Memory saved. 3 decisions logged, 2 patterns captured. Safe to compact."

/memory-update — Run this mid-session when you make a significant decision. Or just say "remember this decision" and Claude logs it to memory/decisions.md immediately, without waiting for session end.

/memory-share — Commits MEMORY.md and the memory/ directory to git, then pushes. Your teammates get the context when they pull.

/memory-audit — Run this monthly. It finds L1 bloat (entries that should have moved to L2), flags contradictions between memory files, and prunes entries that are no longer relevant.

The daily workflow looks like this:

Monday morning:
  git pull
  /memory-load
  → Claude knows what you left off Friday. No re-explaining.

During work:
  Make an architectural decision? → /memory-update
  Context getting long? → /memory-save → /compact → /memory-load

End of day:
  /memory-save
  /memory-share  (if working with a team)
Enter fullscreen mode Exit fullscreen mode

Five minutes of memory hygiene saves 20 minutes of re-orientation at the start of the next session.

Team memory via git

Here's where this gets useful for teams: because memory lives in files, git handles the sync.

Developer A finishes a session, runs /memory-save then /memory-share. That commits a structured record of every decision made, every pattern established, the current task state. Developer B pulls the repo, runs /memory-load, and Claude tells them exactly where the project stands.

No Slack thread explaining "why did we choose X over Y." The answer is in memory/decisions.md with the date, the alternatives considered, and the reasoning.

The same mechanism works when you switch machines, when you onboard a new team member, or when you come back to a project after two weeks away. The memory persists because files persist.

One practical note: don't put memory files in .gitignore. Treat them like documentation. They belong in version control.

What you're actually solving

The context compaction problem isn't going away. Context windows are finite. Claude will always need to compact on long sessions.

The question is whether the knowledge built up during a session survives that boundary. Without a memory system, it doesn't. You re-explain things. You re-litigate decisions. Claude makes mistakes it made before because it doesn't know those approaches failed.

With a memory system, compaction becomes a non-event. You run /memory-save, compact, run /memory-load, and pick up where you left off. The decision history is intact. The patterns are intact. The current state is accurate.

The 59+ compactions that developers tracked weren't the problem. The lost context between them was.


I packaged this system into a kit at builtbyzac.com/memory-kit.html. It's 5 Claude Code skills plus the templates for MEMORY.md and the memory/ files, $19. Install takes about 10 minutes. If you'd rather build it yourself from this post, the structure is all here.

Top comments (0)