Sync Claude Code memory across multiple machines using a GitHub private repo + git-crypt. No self-hosted servers, no SSH tunnels, no Tailscale -- just GitHub and an encryption key.
Here's a fun one: you spend an hour teaching Claude your coding preferences, your project architecture, your pet peeves about trailing whitespace. Claude dutifully remembers all of it -- in ~/.claude/projects/ as Markdown files. Beautiful.
Then you SSH into your home server, fire up a tmux session, and Claude greets you like a stranger. Because it is. Different machine, different memory, zero shared context. All those carefully accumulated preferences? Trapped on whatever machine you happened to be using when you taught them. And the machine you're on right now has no idea any of that ever happened.
We got tired of re-explaining ourselves, so we built this. One GitHub repo, git-crypt, and a hook that makes Claude's memory follow you across machines -- no self-hosted infrastructure required.
GitHub as the hub: your memory lives in an encrypted private repo. Every machine syncs to it. Think of it as a git remote, except the only thing it stores is Claude's opinions about your code style -- and it's encrypted at rest so GitHub can't read them either.
graph TD
GH["GitHub Private Repo<br/>(encrypted at rest via git-crypt)"]
M1["Machine 1<br/>(laptop)"]
M2["Machine 2<br/>(desktop)"]
MN["Machine N<br/>(future machine)"]
M1 -- "git push/pull<br/>(HTTPS or SSH)" --> GH
M2 -- "git push/pull<br/>(HTTPS or SSH)" --> GH
MN -. "git push/pull<br/>(HTTPS or SSH)" .-> GH
style GH fill:#4a9eff,stroke:#2d7cd6,color:#fff
style M1 fill:#50c878,stroke:#3aa35e,color:#fff
style M2 fill:#50c878,stroke:#3aa35e,color:#fff
style MN fill:#999,stroke:#666,color:#fff,stroke-dasharray: 5 5
How it actually works:
- Claude writes a memory file ->
PostToolUsehook fires -> autogit commit + push. You don't do anything. - Push is fire-and-forget -- no internet? Push silently skips. No errors, no drama.
- Next time you're online, changes sync up automatically. It just works. (We're as surprised as you are.)
- Memory files are encrypted with AES-256 via git-crypt. On GitHub they're binary gibberish. On your machines, they're plain Markdown.
Got a new machine? Clone the repo, unlock with your key file, done. ./setup.sh join handles the details.
Before we get started, you'll need a few things. Nothing exotic:
- Two or more macOS/Linux machines (sorry, we haven't tested on Windows -- PRs welcome if you're braver than us)
- Git, Python 3,
jq, andgit-crypton all machines (brew install jq git-crypt) - GitHub CLI (
gh) installed and authenticated (gh auth login) - Claude Code installed on all machines
No SSH keys to distribute, no bare repos to maintain, no Tailscale to configure. If you can push to GitHub, you're good.
git clone https://github.com/KerberosClaw/kc_claude_memory_sync.git ~/dev/kc_claude_memory_sync
cd ~/dev/kc_claude_memory_sync
./setup.sh initThe script handles the boring parts:
- Creates a private repo on GitHub via
gh - Initializes git-crypt and exports the encryption key
- Scoops up your existing memory files into the repo
- Pushes everything (encrypted) to GitHub
- Configures
autoMemoryDirectoryand the sync hook in Claude Code
After init, back up the key file. The script tells you where it is. Copy it to your other machines via scp, USB, AirDrop, carrier pigeon -- whatever you trust.
git clone https://github.com/KerberosClaw/kc_claude_memory_sync.git ~/dev/kc_claude_memory_sync
cd ~/dev/kc_claude_memory_sync
./setup.sh joinThis will:
- Clone the memory repo from GitHub
- Unlock it with your git-crypt key
- Merge any local memory files, with conflict detection so nothing gets silently lost
- Configure
autoMemoryDirectoryand the sync hook
# First machine
./setup.sh init --repo-name kc_claude_memory --local-repo ~/dev/kc_claude_memory
# Additional machines
./setup.sh join --repo-url https://github.com/user/repo.git --key-file /tmp/key --local-repo ~/dev/kc_claude_memoryA CLAUDE.md is included in the project. Just tell Claude Code "help me set up memory sync" and it will read the instructions and handle everything. We wrote the automation guide so you don't have to think. You're welcome.
A PostToolUse hook watches for Write and Edit tool calls. When Claude modifies a memory file, it:
- Reads
autoMemoryDirectoryfrom settings to find the memory repo - Checks if the written file is inside that repo
git add+git commit+git pull --rebase+git push-- or if GitHub is unreachable, quietly moves on with its life
When you run join, your local memories meet the repo's memories for the first time. It can be awkward. Here's how we handle it:
| Scenario | What Happens |
|---|---|
| File only on this machine | Added to repo -- the more memories, the merrier |
| File only on repo | Kept as-is |
| Same filename, same content | Skipped -- great minds think alike |
| Same filename, different content | Both kept (*_conflict.md for you to review -- we're not going to pick favorites) |
MEMORY.md |
Auto-merged: entries combined and deduplicated |
All *.md files in the memory repo are encrypted using git-crypt (AES-256). This means:
- On GitHub: binary gibberish. Not even GitHub can read your memories.
- On your machines: plain Markdown, as if encryption doesn't exist.
- The encryption/decryption is transparent -- git handles it automatically via clean/smudge filters.
You need the key file to unlock the repo. Lose the key, lose access. Back it up.
cd ~/dev/kc_claude_memory_sync
./sync.sh sync # pull then push
./sync.sh pull # pull only
./sync.sh push # push only
./sync.sh status # show sync statusWhen you're not sure if your memories made it across, status gives you the full picture:
$ ./sync.sh status
=== Claude Memory Sync Status ===
Remote: https://github.com/your-username/kc_claude_memory.git
Last commit: 2026-03-21 14:32:05 +0800
sync: update memory
Local changes: none
Sync state: up to date
No more wondering "did my laptop push before I closed the lid?" -- just run status.
config.sh is auto-generated by setup and git-ignored (because it contains your repo URL):
# Claude Memory Sync Configuration
REPO_URL="https://github.com/your-username/kc_claude_memory.git"
LOCAL_REPO="~/dev/kc_claude_memory"That's it. Two variables. The old version had YAML with SSH hosts, fallback IPs, timeouts, and bare repo paths. We don't miss it.
kc_claude_memory_sync/
├── setup.sh # Setup wizard (init / join)
├── sync.sh # Manual sync + status
├── uninstall.sh # Remove settings and hook config
├── hooks/
│ └── memory-sync.sh # Claude Code PostToolUse hook
├── lib/
│ ├── common.sh # Shared functions (colors, locking, config)
│ └── merge-memory.sh # Memory file merge + MEMORY.md dedup
├── specs/ # Historical spec-driven development artifacts
├── config.example.sh # Example configuration
├── CLAUDE.md # Claude Code automation instructions
├── LICENSE
├── .gitignore
└── .gitattributes
cd ~/dev/kc_claude_memory_sync
./uninstall.shRemoves autoMemoryDirectory and the hook entry from settings.json. The synced repo is preserved in case you change your mind. (They always come back.)
We believe in setting expectations, so here's what this tool doesn't do:
- Key management is on you -- lose the git-crypt key and you can't decrypt your memory on new machines. The encrypted blobs on GitHub are useless without it. Back up the key.
- No auto-pull on session start -- Claude Code doesn't have a session-start hook, so we can't magically pull when you open a new conversation. Workaround:
alias claude='cd ~/dev/kc_claude_memory_sync && ./sync.sh pull && cd - && claude'or just let the next push pull first. - Last write wins -- if two machines edit the same memory file simultaneously, the last push wins. In practice this rarely happens because Claude Code sessions are sequential, but now you know.
MIT