KennyG
OMVPPaaS CMS Developer Certification
Sep 7, 2026
visibility 476
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
Migrating ContentArea.FilteredItems in Optimizely CMS 13

If you have

Adnan Zameer | Oct 19, 2026 |

Categories have landed in Optimizely SaaS CMS: structure, translate, tag and query

Categories arrived in Optimizely SaaS CMS at the end of August, with Graph support alongside. I have wanted a proper taxonomy in SaaS since the fir...

Minesh Shah (Netcel) | Oct 19, 2026

Exploring Cached Templates and Cursor Pagination in the Optimizely Graph C# SDK

While experimenting with the  Optimizely Graph C# SDK (13.3.0) in my local Optimizely CMS 13 Alloy project, I came across two interesting findings:...

Mardy | Oct 19, 2026

EPiServer.ImageLibrary.ImageSharp 2.0.7 delisted from NuGet feed

We have delisted version 2.0.7 of the EPiServer.ImageLibrary.ImageSharp package from the NuGet feed due to a packaging issue. This version was...

Phu Nguyen | Oct 19, 2026