A complete beginner's guide to making Claude Code remember everything across every session — with CLAUDE.md, primer.md, memory.sh, and git hooks.
curl -sL tunerlabs.com/tools/claude-code-memory/install.sh | bash
Watch the walkthrough
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.
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.
Before diving into each layer, here's what we're building and how the pieces fit together:
Your permanent rules, code style preferences, and project conventions. You write this once. It never changes automatically.
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.
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.
One line added to your git workflow. Every commit is automatically logged to a memory file. Zero effort audit trail.
The foundation of the entire memory stack. Claude Code reads this automatically before every session.
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.
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.
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.
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.
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".
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.
# 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
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.
Claude rewrites this at the end of every session, so the next session always starts with full context.
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.
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.
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.
Claude completely replaces the contents of primer.md. Not appends — replaces. This ensures it stays tight, current, and never grows out of control.
Here's an example of what Claude might write into your primer.md at the end of a 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
Create this file manually when you first set up the system. It can be very simple to start:
# Project State
Project is in early development.
No sessions completed yet.
Starting fresh — see CLAUDE.md for project overview.
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.
A shell script that pulls fresh data from git and injects it as context at the start of every session.
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.
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.
Create a file called memory.sh in your project root with the following contents:
#!/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."
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:
chmod +x memory.sh
You only need to do this once.
Before opening Claude Code, run:
bash memory.sh
This prints a block of current git context to your terminal.
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.
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.
Zero-effort audit trail. Every commit automatically logged to a memory file.
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.
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.
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:
cd .git/hooks
Inside .git/hooks/, create a new file called exactly post-commit (no extension) with this content:
#!/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"
Just like memory.sh, the hook needs execute permission. From inside the .git/hooks/ folder:
chmod +x post-commit
Done. Now every single commit you make will automatically add a line to project-memory.md.
Go back to your project root and commit the new file so it's tracked:
cd ../.. # back to project root git add project-memory.md primer.md git commit -m "Add Claude Code memory stack"
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.
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:
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/ ├── 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
| 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 |
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.
| 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 |
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.
chmod +x memory.shchmod +x post-commit