Mac-to-Mac Migration (same Claude login)
The one truth that changes everything
Signing into Claude on the new Mac restores your account and subscription — nothing else. Your skills, agents, MCP server definitions, plugins, chat history, and all project data are local files (mostly under ~/.claude, ~/.agents, and your code folders). If the old Mac is wiped before those are captured, they are gone. So this is not "zip and hope" — it is a deliberate capture → verify → restore → reconcile project.
Two facts shape the entire approach:
- The new Mac's username is usually different (
pw→dhiraj,pranav, etc.). Every absolute/Users/<old>path must be rewritten. If you can set the new account's short name to match the old one, you delete this entire problem — recommend it early. - The two Macs are rarely online at once, so Migration Assistant over the network often isn't an option. A checksummed archive bundle on cloud/external storage is the reliable path.
Step 0 — figure out which side the user is on, and route
- On the OLD Mac (still have it): you are doing Phase 1 — Prepare & Back Up. Read
references/old-mac-prep.mdand drivescripts/migrate-out.sh. There may be a hard deadline (device return/reset) — establish it and work backward. - On the NEW Mac (old one gone/wiped): you are doing Phase 2 — Restore & Reconcile. Read
references/new-mac-restore.mdand drivescripts/migrate-in.sh. - Debugging a half-done move ("chats say working-directory-not-found", "my skills vanished"): jump to the reconciliation sections in
references/new-mac-restore.md— this is almost always stale/Users/<old>paths in session metadata or broken skill symlinks.
Always confirm the old and new usernames (whoami, echo $HOME) before touching path-rewrite logic.
The five non-negotiable doctrines
These are the hard-won lessons. Violate them and data is silently lost.
Extract, never
git clone. Restore every project by extracting its archive with.git/intact — not by cloning from GitHub. Cloning gets the code but silently drops gitignored data: Obsidian/RAG vaults,.lancedbvector indexes, git stashes, un-pushed commits, and repos with no remote at all. Those exist only inside the archive. (Clone is a fallback only for a repo that is verified clean and fully pushed.)Username = everything. Nearly all migration pain is absolute
/Users/<old>paths. Derive the rewrite target from$HOME, notwhoami(they can disagree during an account rename). Rewrite in two ordered passes: (A)/Users/<old>/ → /Users/<new>/, then (B) relocate project roots into their new home (e.g.~/dev). Then hunt the places a config-only rewrite misses (see doctrine 4).Verify, don't assume — in both directions. Checksum the bundle and re-download one archive to verify before wiping the old Mac (silent cloud corruption on a multi-GB upload is a real risk). On the new Mac, don't trust "it ran" — grep for leftover
/Users/<old>, find broken skill symlinks, count.lancedbdirs, and confirm chat history actually loads.A restore script rewrites config, not everything. After the script runs,
/Users/<old>typically survives in three places that break things and must be fixed separately: (a) project source code, (b) bare/Users/<old>with no trailing slash, and (c) Claude Desktop session metadata (thecwd/originCwd/worktreePathfields in~/Library/Application Support/Claude/…-sessions/*.json, plus the~/.claude/projects/-Users-<old>-…history slugs). Stale sessioncwds cause "Working directory no longer exists"; and once those are fixed, a transcript filed under a slug that no longer matches its repairedcwdcauses the distinct "Session history unavailable" (chat opens, but empty) — fixed by aligning the transcript to the cwd slug (migrate-in.shstep 7b). And (d) machine-local background agents —~/Library/LaunchAgents/*.plist(the "Login Items → Allow in the Background" items) don't travel with a sign-in, use literal paths launchd won't expand, and macOS re-blocks them until re-approved;migrate-in.shstages them path-rewritten for review (seereferences/new-mac-restore.md§6b).Done means redundant, not "it works." A single Mac + a soon-to-be-deleted cloud bundle is the same single point of failure that motivated the migration — worse if it's a corporate/MDM-managed machine that IT can wipe. Before deleting the backup, make sure the irreplaceable data (vaults,
.lancedb, no-remote repos, stashes) lives in a second place (external SSD / Time Machine). And replace hard-coded/Users/<old>paths in~/.claude/settings.jsonhooks with$HOME/~so the next move is painless.
Phase 1 — OLD MAC: prepare & back up
Full detail + rationale in references/old-mac-prep.md. The arc:
- Set the deadline & keep awake. If the Mac is being returned/reset, note the hard cutoff; run
caffeinate -dis &so uploads don't stall. - Harden — is Claude the only bridge? Confirm nothing critical secretly depends on an editor/tool you're removing (check
~/.zshrc/PATH, LaunchAgents/cron/git-hooks,~/.claude.json+ MCP configs). Usually the tool you're worried about is "a leaf, not a hub" — the real risk is the reverse: the wipe destroys~/.claude, on-disk-only secrets, and un-pushed git work. - Inventory what matters. List the project folders to take (a Finder tag like Green is a good census signal). For each, separate regenerable (
node_modules,.venv,target,dist) from irreplaceable (source,.git, gitignored vaults +.lancedb, stashes). - Push code to remotes where sensible (gives redundancy + sidesteps rewrites for code) — but this does NOT replace the archive for anything gitignored or no-remote.
- Build the bundle with
scripts/migrate-out.sh(parameterize your folders): one.tgzper project (regenerables excluded,.git+vaults included), plus~/.claude(stripped of caches),~/.agents,~/.claude/projects(history), the Claude Desktop session store, dotfiles/secrets, and~/Library/LaunchAgents(background "Allow in the Background" agents). Critical catch: the52 "symlinked" skills point outside `/.claudeinto/.agents/skills//.agents` too or they restore as broken links.— archive - Checksum & verify round-trip. Generate
CHECKSUMS.txt(shasum -a 256); after upload, re-download one file and verify it matches. - Write the runbook + a manifest recording the old username, the project→destination map, and which secrets are intentionally not backed up (gh token, OAuth keys — they live in Keychain and get re-auth'd by hand).
- The gate — do NOT reset until: the cloud shows the bundle fully uploaded (check byte sizes, not just "syncing"), and you've written down the credentials you'll re-enter (Apple ID, Google + 2FA, GitHub, each MCP login).
Phase 2 — NEW MAC: restore & reconcile
Full detail in references/new-mac-restore.md. The arc:
- Sanity + identity.
whoami,echo $HOME,df -h /(need headroom). If the account name differs from the old one, decide now whether to rename it (cleanest) or proceed and rewrite paths. If an admin renames the short name, do it before the restore and confirmwhoamimatches the new$HOME. - Prerequisites. Homebrew, then
git gh node uv(+rust/goif your projects need them), the Obsidian app for vault re-add, andgh auth login. - Get + verify the bundle locally (force a full download off cloud stubs), then
shasum -a 256 -c CHECKSUMS.txt— every line must sayOK. - Dry-run then run
scripts/migrate-in.sh. It verifies checksums, restores dotfiles/~/.claude/~/.agents/history/Desktop sessions, extracts projects into~/dev, rewrites paths (identity from$HOME), rewrites Desktop sessioncwds + history slugs, and aligns each chat transcript with its session's cwd slug (step 7b — so history actually loads) — the gaps the naive script misses. - Reconcile the completeness gaps (doctrine 4) — the script bundled here handles them, but verify: no
/Users/<old>in code/config, sessioncwds all resolve, chats reopen with history. - Per-repo deps + the india-invest-style exceptions, re-add the Obsidian vaults in the app, re-auth MCP servers (the script prints the list).
- Verify (doctrine 3) and establish redundancy (doctrine 5).
Bundled resources
scripts/migrate-out.sh— parameterized bundle builder (run on the OLD Mac). Reads a small config block (folders, home dir); excludes regenerables; includes.git+vaults; archives~/.claude/~/.agents/history/desktop-sessions/dotfiles; writesCHECKSUMS.txtand amanifest.jsonrecording the old username + project map. Also archives~/Library/LaunchAgents(background agents) + a loaded-agent inventory.scripts/migrate-in.sh— the restore script (run on the NEW Mac).--dry-runcapable, idempotent, checksum-gated,$HOME-derived identity, pipefail-safe path rewriting, includes the Desktop-config + session-cwd+ history-slug rewrites, plus step 7b that aligns transcripts to their cwd slug ("Session history unavailable" fix), stages background LaunchAgents (path-rewritten) for review, and a broken-symlink verifier.scripts/verify.sh— post-restore verification (leftover paths, broken symlinks,.lancedbcount, session-cwd resolution [4], misplaced-transcript count [4b], chat-history sanity).references/old-mac-prep.md— the full prepare-and-back-up methodology + rationale.references/new-mac-restore.md— the full restore + reconciliation methodology, including the session-cwd/slug fixes and the resilience audit.references/lessons-and-pitfalls.md— the concentrated wisdom: every trap this process hit and how to avoid it.
Read the relevant reference before driving its script. Confirm usernames and the folder list with the user before running anything destructive, and always --dry-run the restore first.