KennyG
Sep 7, 2026
visibility 302
star star star star star
(0 votes)

Two Machines, One Memory

How to keep a Claude Code memory store in lockstep across two machines, using nothing but git and two hooks.

We've been running Claude Code against our Optimizely CMS 12 / Commerce Connect 14 platform for a while now, and one thing surprised me pretty quickly: it gets good at our codebase, not just at coding.

It remembers that a migration has to run before a specific InitializableModule or things break in strange ways. It remembers feature flags nobody has cleaned up yet. It remembers that the last DXP copydown wiped out a SiteDefinition nobody noticed until hours later.

Then I'd switch to my other machine, start a new session — and all of that was gone. Same agent, same codebase, back to square one.

The problem

Claude Code's memory is just files on disk. On one machine that works well — it accumulates and the agent gets more useful over time. On two machines, you get two memories, both growing, both changing, neither aware the other exists. That's worse than no memory: at least with nothing, you know where you stand.

Make the memory folder a repo

The fix isn't clever. Turn the memory folder into its own git repository, and sync it the same way you'd sync any shared state.

# from the memory folder for your project
cd ~/.claude-work/projects/<project-key>/memory
git init
git remote add origin git@github.com:you/claude-memory.git
git add -A && git commit -m "Initial memory store"
git push -u origin main

I did the same for plans/, where the agent writes design notes before larger changes, kept in a repo outside any application repo so it survives branch cleanups and repo resets.

Pull on start, push on end

Claude Code hooks fire on session lifecycle events. A SessionStart hook runs before the session begins; a SessionEnd hook runs after it finishes.

{
  "hooks": {
    "SessionStart": [{
      "hooks": [{ "type": "command",
        "command": "pwsh -File ~/.claude-work/hooks/memory-sync-pull.ps1" }]
    }],
    "SessionEnd": [{
      "hooks": [{ "type": "command",
        "command": "pwsh -File ~/.claude-work/hooks/memory-sync-push.ps1" }]
    }]
  }
}

The naive first version of the pull hook:

cd $env:MEMORY_REPO
git pull --rebase --autostash --quiet 2>$null | Out-Null
exit 0

And the push hook:

cd $env:MEMORY_REPO
git add -A
git commit -m "session $(Get-Date -Format o)" --quiet 2>$null
git push --quiet 2>$null

That's the entire mechanism: pull at start, commit and push at end. No manual step, nothing to remember to do.

Hardening the pull

The v1 pull hook swallows every error and always exits 0, on purpose — I didn't want a flaky git pull to block a coding session. The cost is that a failed rebase (a conflict, a detached HEAD) goes completely silent, and every session after that keeps committing memory onto a head that will never push cleanly. I found this a month and sixty-four orphaned commits later.

Two changes close that gap: check repository state after the pull, not just its exit code, and make failures persistent instead of one-shot.

cd $env:MEMORY_REPO

git pull --rebase --autostash --quiet 2>$null | Out-Null

$rebasing = (Test-Path ".git/rebase-merge") -or (Test-Path ".git/rebase-apply")
$detached = (git symbolic-ref -q HEAD) -eq $null

if ($rebasing) {
    git rebase --abort 2>$null
    "sync-failed: rebase aborted at $(Get-Date -Format o)" | Out-File -Append .sync-status
} elseif ($detached) {
    git checkout main --quiet 2>$null
    "sync-failed: recovered from detached HEAD at $(Get-Date -Format o)" | Out-File -Append .sync-status
}

exit 0

Because the previous session already committed its changes locally before the pull ran, aborting a failed rebase only discards the failed replay — never the memory itself.

cd $env:MEMORY_REPO
git add -A
git commit -m "session $(Get-Date -Format o)" --quiet 2>$null

git push --quiet
if ($LASTEXITCODE -eq 0) {
    Remove-Item .sync-status -ErrorAction SilentlyContinue
} else {
    "push-failed at $(Get-Date -Format o)" | Out-File -Append .sync-status
}

Why this matters: A successful pull doesn't prove sync is healthy — a successful round trip does. The marker file only clears on a real push, so a broken sync keeps surfacing at the next session start instead of disappearing after one quiet failure.

Why it matters here

None of this is Optimizely-specific — it's git and two PowerShell scripts. It matters for a project like this one because Optimizely platforms live for years and accumulate real tribal knowledge: which reindex is safe to run unattended, which scheduled jobs have bitten you before, which feature flags are dead weight nobody's removed, which migration has an ordering dependency nobody wrote down. An agent that keeps that knowledge across machines is worth a lot more than one that resets every time you switch machines.

What to set up

  • Memory folder → its own git repo, remote pushed once
  • Plans folder → its own git repo, outside any application repo
  • SessionStart hook → pull, then verify repo state and mark failures persistently
  • SessionEnd hook → commit, push, clear the marker only on a successful push

Curious whether others syncing AI memory across machines have run into the same failure mode, or found a cleaner way to detect it. I'd like to compare notes.

Sep 07, 2026

Comments

error Please login to comment.
Latest blogs
Composition Over Inheritance: Why Your Content Contract Is Not a Base Class

Inside the structural conformance pattern in SaaS CMS — from server-side inheritance to pipeline governance. A contract in Optimizely SaaS CMS is...

Vipin Banka | Sep 19, 2026

Optimizely Opti ID Integration: Why the API Key Was Missing from the DXP Management Portal

While working on an Optimizely CMS 12 Opti ID integration recently, we ran into an issue that initially appeared to be related to SSO authenticatio...

Madhu | Sep 18, 2026 |

Extending a Contract: What You Can Override

Contracts share fields across content types — but not every field setting can be overridden. Push the same contract onto several components, and CM...

Chirag Khanna | Sep 18, 2026 |

Building a Blazor Server UI Inside the Optimizely CMS 13 Admin Shell

What I learned shipping a Blazor Server UI inside Optimizely CMS 13 — Razor components in a NuGet package, the Blazor hub next to MapContent(),...

Stanisław Szołkowski | Sep 17, 2026 |