Feature request: stable project identity + first-class cross-machine sync for auto memory and portable profile state
Problem
Auto memory is keyed by the munged absolute path of the project directory
(~/.claude/projects/-home-user-projects-foo/memory/). The same project
checked out on two machines with different home layouts — Linux
(/home/user/...) and macOS (/Users/user/...) — therefore gets two
unrelated memory directories. On top of that, the docs state that auto
memory is machine-local by design ("Files are not shared across machines or
cloud environments", https://code.claude.com/docs/en/memory.md).
For anyone who works on the same projects from more than one machine daily,
this splits the assistant's accumulated knowledge: feedback, project
context, and preferences learned on machine A simply don't exist on
machine B.
Our current workaround (works, but is all hand-rolled)
~/.claudeis a git repository pushed to a private GitHub remote.- Memory lives in a canonical, machine-independent bucket inside that repo
(~/.claude/projects/_synced/projects/<workspace>/memory/).
- The live munged-path memory directory on each machine is a symlink
into that bucket, bootstrapped idempotently on first run per machine.
- A
/syncskill is the only actor allowed to commit/pull/push, with
strict semantics (see below).
- A read-only
SessionStarthook diagnoses drift (behind/ahead vs origin,
dirty tree, missing symlink) and reports it at the start of every
session, without ever acting.
Proposal
Two complementary pieces:
- Stable project identity. Key project state (auto memory, per-project
settings) on something machine-independent: the normalized git remote
URL, or a declarable project ID (e.g. in .claude/), with the munged
path as fallback for repos without a remote. This alone removes the need
for the symlink layer.
- An official sync mechanism for the portable part of the profile
(memory, settings, skills, agents, global CLAUDE.md). Account-based sync
would be ideal; a git-backed claude sync command would already cover
the need. Semantics we converged on after real-world use, offered as
prior art:
- fast-forward only, never auto-merge — on divergence, stop and show
both sides;
- a dirty working tree blocks the handoff — uncommitted work is the
only state the other machine cannot observe (it sees ahead/behind,
not your working tree), so it must be surfaced loudly, not noted in
passing;
- a read-only drift check at session start, separate from the action.
Why this is tractable
The auto memory format is already sync-friendly: one file = one fact, plus
a regenerable MEMORY.md index. File-level sync is nearly conflict-free by
construction — the index can be rebuilt after merge. The design is already
there; it's only the transport that's missing.
3 Comments
Bumping for triage — this issue doesn't seem to have been picked up.
Would also love to see this.
What works today, and a small fix for the rest. Checked on 2.1.278.
Memory: solvable today. Pass
autoMemoryDirectorythrough--settings, pointing to<project root>/.claude/memorywith the root computed at launch (git rev-parse --show-toplevel). Memory then lives in the project and syncs with it, and each machine resolves its own absolute path when Claude Code starts, so the path-derived key no longer matters for memory. Tested wrappers for bash/zsh and PowerShell are in #25739.Transcripts: the identity switch exists, in the wrong place. Since 2.1.234,
CLAUDE_CODE_PROJECT_DIR_NAMEreplaces the path-derived<project>directory with a chosen name, for transcripts and memory alike: a declarable project ID. Per the docs it is ignored unlessCLAUDE_CONFIG_DIRis also set, because a name that does not vary with the working directory would otherwise merge every project into one store.The fix: read that name from the project's own
.claude/settings.json. A name carried by the project differs from project to project, so nothing merges; it syncs with the project, so it is the same on every machine; and it does not depend on where the project is mounted. The mechanism already exists; only the source of the name is missing.