A git-backed auto-memory system for Claude Code
How to keep Claude Code's per-project memory store from dying with your laptop — a private GitHub repository, a symlink, and one Stop hook.
Claude Code keeps a persistent memory directory per project on disk. Each time a session ends, anything Claude has chosen to remember — user preferences, project context, references to external systems — gets written there as a small markdown file, and Claude reads those files at the start of the next session to recover its bearings.
The mechanism is reliable. The storage layer is not. Those files live on a single machine, in a directory most users never look at, and a clean reinstall or a failed disk takes the entire memory store with them.
After accumulating a few months' worth of memory across active projects, the cost of losing that store became uncomfortable. Re-explaining a working environment to a fresh Claude is the kind of friction the memory system exists to remove in the first place. The solution is small enough to fit in a paragraph: replace the local memory directory with a symlink into a private git repository, and let a Stop hook commit and push at the end of every session.
How auto-memory is stored on disk
Claude Code keeps memories at:
~/.claude/projects/<project-slug>/memory/
Where <project-slug> is a hash derived from the working directory of the session. Different projects have different memory stores. Inside the directory, each memory is a markdown file with YAML frontmatter (name, description, type, and the body text), and a single MEMORY.md index points at all of them.
That layout is git-friendly out of the box. Claude Code does not care whether the directory is a regular folder or a symlink to somewhere else, only that it exists and is writable.
The setup
Create a private GitHub repository to hold the memory store. Name it something descriptive — anything that signals "this is the memory store for one laptop's Claude Code sessions" works:
cd ~/Desktop/projects
gh repo create claude-laptop-memory --private --clone \
--description "Private memory store for Claude Code sessions on this laptop"
gh repo create --clone creates the repository under your authenticated GitHub account and clones it into the current directory.
Then move the existing memory directory aside as a backup, and symlink the new repository in its place:
PROJECT_SLUG=$(ls ~/.claude/projects | head -1) # or pick the right one explicitly
mv ~/.claude/projects/$PROJECT_SLUG/memory \
~/.claude/projects/$PROJECT_SLUG/memory.backup-$(date +%Y%m%d)
ln -s ~/Desktop/projects/claude-laptop-memory \
~/.claude/projects/$PROJECT_SLUG/memory
The backup is the cheap insurance step. If anything goes wrong, the original files are still on disk under the .backup-... path, and reverting is a mv away.
At this point, the auto-memory directory and the working tree of the git repository are the same files. Anything Claude Code writes lands in the repository. The remaining piece is to commit and push automatically on every session end.
The Stop hook
Claude Code accepts hook configuration in ~/.claude/settings.json. The Stop hook fires when a session ends. Adding the following entry causes every session to commit any pending changes and push them upstream:
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "<see below>"
}
]
}
]
}
}
The command value is a single shell line. Broken out for readability, it does this:
cd ~/Desktop/projects/claude-laptop-memory \
&& if [ -n "$(git status --porcelain 2>/dev/null)" ]; then \
git add -A \
&& git -c user.name='your-name' \
-c user.email='your.email@example.com' \
commit -m 'memory: auto-sync' --quiet \
&& git push origin main --quiet; \
fi; \
exit 0
Three details are worth attention:
- The
git status --porcelainguard prevents an empty commit on every Stop. If the session did not write any new memory, the hook is a no-op. - The trailing
exit 0is intentional. If the push fails — bad network, missing credentials, a transient GitHub error — the hook should not block the user from finishing their work in Claude Code. The next session will catch up. - The git author identity is set inline using
-c user.name='...' -c user.email='...', so the hook does not depend on the user's global git configuration, which may already be set up for unrelated work.
Once the hook is in place, every session that touches memory ends with a clean commit pushed to GitHub. There is nothing more to remember.
What the change actually buys
Three things became different the day the hook started firing:
Durability. The memory store is backed up to GitHub on every change. A laptop failure costs nothing. A clean reinstall is a single git clone away from full continuity.
Cross-machine continuity. Cloning the repository onto a second machine and pointing its auto-memory symlink at it gives that machine the same set of memories. Claude Code on the new machine inherits everything the previous one knew.
Audit and review. Memory entries become commits with timestamps. Reviewing what Claude has been remembering — and editing or removing entries that turned out to be wrong — becomes the same workflow as reviewing any other code change. git log and git blame answer when each memory was added and what triggered it.
Caveats worth knowing
The hook configuration in settings.json is read at session start, not at the time of writing. Changes to the hook take effect on the next session, not the current one. Run /hooks in an interactive Claude Code session to reload mid-session if a change is needed immediately.
Every Stop event triggers the hook, including ones that did not touch memory. The git status --porcelain guard handles the empty case efficiently, but it still costs a shell invocation. The cost is not zero; it is also small enough to be unmeasurable in any realistic workflow.
The full system is approximately forty lines of shell, a JSON snippet, and a private git repository. The Claude Code memory store stops being a single-machine artifact and starts behaving like every other piece of state worth keeping.