KennyG
Sep 7, 2026
visibility 23
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
Opticon New York & The Strategy on Upgrading to CMS 13 & Commerce 15

Overview Well, another exciting Opticon in New York City wrapped up last week and it was certainly one of the most fun and exciting ones I've been...

Scott Reed | Sep 7, 2026

A Look at the New Optimizely Certification Program

Optimizely has introduced a new certification program. So far, I have completed Optimizely Foundations and the Core certifications for Opti ID, Age...

Pär Wissmark | Sep 7, 2026 |

New addon puts the optimization GUI for Optimizely Graph back in the CMS

I wanted the Graph UI for Pinned Results and Synonyms inside the CMS, so I created another addon.

Tomas Hensrud Gulla | Sep 1, 2026 |