KennyG
Sep 7, 2026
visibility 342
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
Optimizely Graph - Provisioning, User Management & Developer Instances

Upgrading to Optimizely CMS 13 marks a major architectural shift as Optimizely Graph takes over as the central query and delivery engine, retiring...

Scott Reed | Sep 28, 2026

An (unofficial) MCP server for Optimizely Data Platform (ODP)

I wanted to ask Claude some questions about session setup data in Optimizely Data Platform (ODP) without writing GraphQL every time. There's no...

Jacob Pretorius | Sep 28, 2026

Admin UI for Optimizely SaaS CMS Integrations Without Extra Tools: Preview-Based Dashboards, OCP UI Extensions and Keeping Them Private

On PaaS we extended the CMS UI with custom admin panels; SaaS CMS has no such hooks. Here is a workaround: an admin UI for external content...

Szymon Uryga | Sep 27, 2026 |

Personalisation in CMS 13 when you go headless: variations in the CMS, decisions in Experimentation

Back in February I wrote about personalisation in CMS 13 using Audiences . Everything in that post still holds, with one condition I should have ma...

Minesh Shah (Netcel) | Sep 24, 2026