Beginner's Guide · 2025

Never Lose Context Again The Four-Layer Memory Stack

A complete beginner's guide to making Claude Code remember everything across every session — with CLAUDE.md, primer.md, memory.sh, and git hooks.

📁 4 files to create ⏱ ~20 min setup 🎯 All project types ✅ Beginner friendly
curl -sL tunerlabs.com/tools/claude-code-memory/install.sh | bash
One command · macOS & Linux · Auto-detects your stack · Safe to re-run

Watch the walkthrough

📚 Claude Code Series

Every developer who uses Claude Code hits the same wall. You have an amazing, productive session — you build things, solve problems, make decisions. Then you close your terminal. Tomorrow, when you open a new session, Claude Code has forgotten everything. It doesn't know what you built, what's broken, or where you left off. You spend the first 10 minutes re-explaining your project. Again. This guide shows you how to fix that permanently.

⚠ The Core Problem

Claude Code has no memory between sessions by default. Every time you open a new session, it starts completely fresh. Your rules, your preferences, your project history, your next steps — all gone. The only thing that persists is what you explicitly give it at the start of each session.

The solution is a four-layer memory system made up of simple text files and a shell script. Once set up, it takes care of itself automatically. Let's build each layer from scratch, explaining every single concept along the way.

The Four Layers at a Glance

Before diving into each layer, here's what we're building and how the pieces fit together:

Layer 1 · Static

CLAUDE.md

Your permanent rules, code style preferences, and project conventions. You write this once. It never changes automatically.

Layer 2 · Dynamic

primer.md

The current state of your project. Claude rewrites this at the end of every session, so it's always up to date when you return.

Layer 3 · Live

memory.sh

A shell script you run at the start of a session. It pulls fresh data from git — recent commits, changed files, errors — and injects it as context.

Layer 4 · Automatic

Git Hook

One line added to your git workflow. Every commit is automatically logged to a memory file. Zero effort audit trail.

How the layers feed into Claude Code
CLAUDE.md — rules & preferences
primer.md — current project state
memory.sh — live git context (injected at launch)
git hook — running commit log
Claude Code — fully informed, every session

CLAUDE.md — Your Permanent Rulebook

The foundation of the entire memory stack. Claude Code reads this automatically before every session.

What is CLAUDE.md?

When you run Claude Code inside a project directory, it automatically looks for a file called CLAUDE.md at the root of your project. If it finds one, it reads it before doing anything else. Think of it as a "briefing document" you hand to Claude at the start of every session — describing who you are, how you like to work, and what your project is about.

This file is static, meaning Claude Code reads it but does not modify it. You write it once, and it silently applies to every session forever. It's the foundation of the entire memory stack.

What should CLAUDE.md contain?

1. Project Identity

Describe your project in plain language. What does it do? Who uses it? What tech stack does it use? Claude should never need to ask "so what is this project?" if you answer this here.

2. Coding Conventions

Do you use tabs or spaces? Single quotes or double quotes? Do you write TypeScript or JavaScript? Do you prefer functional components or classes in React? Define these once here, and Claude will respect them forever.

3. Architectural Decisions

Every project has key decisions that shaped its structure. "We chose Postgres over MongoDB because...", "We use Zod for all validation because...", "All API calls go through the /lib/api.ts wrapper because...". Document those decisions so Claude never suggests something that contradicts them.

4. Things Claude Must Never Do

This is often the most valuable section. Examples: "Never use any type in TypeScript", "Never write inline styles", "Never commit secrets or keys", "Never modify the database schema without asking first".

5. The Primer Rewrite Instruction

This is the single most important addition to your CLAUDE.md — the instruction that powers the entire memory stack. More on this in the next section, but you must add it here.

CLAUDE.md
# Project: My SaaS App

## What This Project Is
A subscription-based task manager for small teams.
Backend: Node.js + Express. Database: PostgreSQL.
Frontend: React + TypeScript. Auth: JWT tokens.
Deployed on: Railway (backend), Vercel (frontend).

## Code Conventions
- Always use TypeScript (never plain JS in .ts/.tsx files)
- Use single quotes for strings
- 2-space indentation
- All async functions must have try/catch error handling
- Use named exports, not default exports (except pages)
- API response format: { data, error, status }

## Architecture Rules
- All DB queries go through /src/db/queries/ — never raw SQL elsewhere
- All API calls from the frontend go through /src/lib/api.ts
- Environment variables accessed only through /src/config.ts

## Never Do
- Never use `any` type in TypeScript
- Never store secrets in code — use .env variables
- Never write inline CSS styles
- Never modify the DB schema without discussing it first

## SESSION MANAGEMENT (CRITICAL)
At the end of EVERY session, you must rewrite primer.md completely.
Include:
1. Current state of the project
2. What was accomplished this session
3. Immediate next steps (specific, actionable)
4. Any open blockers or unresolved issues
5. Any important decisions made this session
✅ Beginner Tip

You don't need to write a perfect CLAUDE.md on day one. Start with just the project description and the primer rewrite instruction. Add more rules as you discover things you need Claude to remember.

primer.md — The Living State Document

Claude rewrites this at the end of every session, so the next session always starts with full context.

The problem CLAUDE.md doesn't solve

Your CLAUDE.md is great for permanent rules — things that don't change. But it doesn't know what happened yesterday. It doesn't know you're halfway through implementing a new feature. It doesn't know you hit a bug that's blocking the next step. That's what primer.md is for.

primer.md is a second markdown file in your project root. Unlike CLAUDE.md (which you write and never touch again), Claude Code rewrites primer.md at the end of every session. That means when you start your next session, the very first thing Claude reads is an accurate, up-to-date description of exactly where your project stands.

How it works

1

First session

You create a blank primer.md (or with a short description). Claude reads CLAUDE.md and primer.md at the start, does your work, then at the end of the session it rewrites primer.md with a full summary of the current state.

2

Every session after that

You open a new Claude Code session. It reads CLAUDE.md (your rules) and primer.md (the state written by the previous session). Claude already knows where you are. It handles itself from here.

3

End of every session

Claude completely replaces the contents of primer.md. Not appends — replaces. This ensures it stays tight, current, and never grows out of control.

What a good primer.md looks like

Here's an example of what Claude might write into your primer.md at the end of a session:

primer.md (auto-generated by Claude at end of session)
# Project State — Updated: 2025-06-14

## Current Status
Working on the team invitation flow. The invite email
sending is complete and tested. The invite acceptance
page (/accept-invite) is 70% done — form renders
but the token validation endpoint is not yet wired up.

## What Was Done This Session
- Built POST /api/invites endpoint (creates invite + sends email)
- Created InviteEmail template in /src/emails/InviteEmail.tsx
- Added invite record to the DB (new table: team_invites)
- Wrote unit tests for the invite creation logic (all passing)

## Immediate Next Steps
1. Build POST /api/invites/accept endpoint (validate token, add user to team)
2. Wire up the AcceptInvitePage form to call the accept endpoint
3. Handle expired token case (tokens expire after 48 hours)
4. Add invite list to team settings page (show pending invites)

## Open Blockers
- Resend (email provider) has a sending limit of 100/day on free tier.
  When we get close to launch, we need to upgrade or switch providers.
- The AcceptInvitePage currently redirects to /dashboard after acceptance,
  but if the user is new (not yet registered), they need to go to /onboarding.
  This logic isn't implemented yet.

## Key Decisions Made
- Invite tokens are UUIDs stored hashed in the DB (not plaintext)
- Invites expire after 48 hours (configurable via INVITE_EXPIRY_HOURS env var)
- A user can be re-invited after their previous invite expires

Your initial primer.md

Create this file manually when you first set up the system. It can be very simple to start:

primer.md (your initial version)
# Project State

Project is in early development.
No sessions completed yet.
Starting fresh — see CLAUDE.md for project overview.
⚠ Important

The rewrite instruction in your CLAUDE.md is what makes this work. Claude Code reads CLAUDE.md at session start, sees the instruction to rewrite primer.md at session end, and follows it. Without that instruction in CLAUDE.md, primer.md never gets updated.

memory.sh — Live Git Context at Launch

A shell script that pulls fresh data from git and injects it as context at the start of every session.

What problem does this solve?

primer.md tells Claude what was planned. But between sessions, you might have manually changed files, made commits, or run into errors. Git contains all of that information — but Claude doesn't automatically look at it. memory.sh bridges that gap.

It's a shell script (a small text file of commands that your terminal can run) that you execute once at the beginning of a session. It queries your git history, finds recently modified files, checks for error logs, and assembles all of that into a single block of text that gets passed to Claude Code as initial context.

What is a shell script?

If you've never written one: a shell script is just a plain text file containing terminal commands, one per line. When you run it, your terminal executes them in order. The .sh extension signals that it's a shell script. That's all there is to it.

Creating memory.sh

Create a file called memory.sh in your project root with the following contents:

memory.sh
#!/bin/bash
# memory.sh — Inject live git context into Claude Code at session start
# Run this before starting a Claude Code session: bash memory.sh

echo "=== CLAUDE CODE SESSION CONTEXT ==="
echo ""

# 1. Show current git branch
echo "Current branch:"
git branch --show-current
echo ""

# 2. Show last 5 commits with short messages
echo "Recent commits (last 5):"
git log --oneline -5
echo ""

# 3. Show files changed since last commit
echo "Files modified (not yet committed):"
git status --short
echo ""

# 4. Show files changed in the last 24 hours
echo "Files changed in last 24 hours:"
git diff --name-only HEAD@{1.day.ago} HEAD 2>/dev/null || echo "No changes"
echo ""

# 5. Check for recent error logs (if your project generates them)
echo "Recent errors (last 10 lines of error.log, if it exists):"
if [ -f "error.log" ]; then
  tail -10 error.log
else
  echo "No error.log found"
fi
echo ""

echo "=== END OF SESSION CONTEXT ==="
echo "Paste the above output into Claude Code when starting your session."

How to use memory.sh

1

Make it executable (one-time setup)

Before you can run a shell script, you need to give your terminal permission to execute it. Open your terminal in the project directory and run:

terminal
chmod +x memory.sh

You only need to do this once.

2

Run it at the start of each session

Before opening Claude Code, run:

terminal
bash memory.sh

This prints a block of current git context to your terminal.

3

Give the output to Claude Code

Copy the output from your terminal and paste it into Claude Code as your first message of the session (before asking it to do any work). This gives Claude an up-to-the-minute picture of your codebase state.

✅ Pro Tip

You can also pipe the output of memory.sh directly into Claude Code using the --context flag if your setup supports it. But manually copying and pasting works perfectly well and requires no extra tooling.

Git Hook — Automatic Commit Logging

Zero-effort audit trail. Every commit automatically logged to a memory file.

What is a git hook?

Git hooks are scripts that git runs automatically at specific moments — for example, every time you make a commit. They live in the hidden .git/hooks/ folder inside your project. You can use them to automatically trigger any action when a git event occurs.

For our memory system, we use a post-commit hook: a script that runs automatically every time you run git commit. Its job is simple — log the commit message and timestamp to a file called project-memory.md. Over time, this builds a complete, automatically maintained history of everything that's been committed.

Why is this useful?

primer.md tells Claude about recent sessions. memory.sh gives Claude the last few commits. But what about older history? What decisions were made two weeks ago? What did you change last month? The git hook's project-memory.md file answers these questions with zero effort on your part.

Setting up the git hook

1

Navigate to the hooks folder

Every git project has a hidden folder at .git/hooks/ inside the project directory. The dot at the start of .git makes it hidden, but it's always there. Navigate to it in your terminal:

terminal
cd .git/hooks
2

Create the post-commit file

Inside .git/hooks/, create a new file called exactly post-commit (no extension) with this content:

.git/hooks/post-commit
#!/bin/bash
# Auto-log every commit to project-memory.md

# Get the latest commit hash (short) and message
COMMIT_HASH=$(git log -1 --format="%h")
COMMIT_MSG=$(git log -1 --format="%s")
COMMIT_DATE=$(git log -1 --format="%ci")
MEMORY_FILE="project-memory.md"

# Create the file with a header if it doesn't exist yet
if [ ! -f "$MEMORY_FILE" ]; then
  echo "# Project Memory Log" > "$MEMORY_FILE"
  echo "Auto-generated by git post-commit hook." >> "$MEMORY_FILE"
  echo "" >> "$MEMORY_FILE"
fi

# Append the new commit
echo "- [$COMMIT_DATE] $COMMIT_HASH: $COMMIT_MSG" >> "$MEMORY_FILE"
3

Make it executable

Just like memory.sh, the hook needs execute permission. From inside the .git/hooks/ folder:

terminal (inside .git/hooks/)
chmod +x post-commit

Done. Now every single commit you make will automatically add a line to project-memory.md.

4

Add project-memory.md to git tracking

Go back to your project root and commit the new file so it's tracked:

terminal (from project root)
cd ../..           # back to project root
git add project-memory.md primer.md
git commit -m "Add Claude Code memory stack"
⚠ Note on git hooks and teams

Git hooks are stored in .git/hooks/ which is NOT committed to your repository by default. If you're working in a team and want everyone to have the hook, you'll need to share it manually, or use a tool like husky to manage hooks as part of the codebase.

Putting It All Together

Your project structure after setup and the complete session workflow.

Skip the manual setup. Run the one-line installer to scaffold all four layers automatically:

terminal (in your project root)
curl -sL tunerlabs.com/tools/claude-code-memory/install.sh | bash

Auto-detects your stack, creates CLAUDE.md (with primer rewrite instruction), primer.md, memory.sh, and the git hook. Safe to re-run — skips files that already exist.

Your project structure after setup

your-project/
your-project/
├── CLAUDE.md            ← Layer 1: permanent rules (you write once)
├── primer.md            ← Layer 2: current state (Claude rewrites every session)
├── memory.sh            ← Layer 3: live git context injector (you run at session start)
├── project-memory.md    ← Layer 4: auto-growing commit log (written by git hook)
├── .git/
│   └── hooks/
│       └── post-commit  ← Layer 4: the hook that writes project-memory.md
└── src/                 ← your actual code

Your session workflow

When What you do What happens automatically
Setup (once) Create CLAUDE.md, primer.md, memory.sh, git hook
Session start Run bash memory.sh, paste output to Claude Claude reads CLAUDE.md + primer.md automatically
During session Work normally with Claude Code Git hook logs each commit to project-memory.md
Session end Tell Claude "end session" Claude rewrites primer.md with current state
Next session Run bash memory.sh, paste output Claude picks up right where you left off
✅ How to trigger the primer rewrite

At the end of a session, simply say to Claude: "End of session — please rewrite primer.md now." Claude will read the instruction in CLAUDE.md and write a fresh, comprehensive primer.md for you. Commit the updated file before closing your terminal.

Summary of All Four Layers

File Type Who writes it When it's read What it contains
CLAUDE.md Static You (once) Every session start Rules, conventions, preferences
primer.md Dynamic Claude (every session) Every session start Current project state, next steps, blockers
memory.sh Live You (configure once) You run it at session start Git branch, recent commits, changed files, errors
project-memory.md Automatic Git hook (every commit) Any session where you want history Full timestamped log of every commit

You're Ready to Build Without Forgetting

Set this up once and every future Claude Code session starts with full context — where you are, what's done, what's next, and what changed. No re-explaining. No lost momentum.

  • Create CLAUDE.md with rules + primer rewrite instruction
  • Create an initial primer.md (even just a few lines)
  • Create memory.sh and run chmod +x memory.sh
  • Create .git/hooks/post-commit and run chmod +x post-commit
  • Commit all new files to git
  • At end of first session, ask Claude to rewrite primer.md