Bidirectional rsync between two Macs over SSH — a "work from anywhere" setup that keeps your Claude Code/Desktop environment, Development projects, and shell config coherent across a primary Mac (desktop) and a secondary Mac (laptop).
The secondary pulls from the primary automatically on login, on network change, and hourly. A safety-net reverse-push (secondary → primary) runs first each time so --delete never wipes work you did on the laptop. A separate primary-side script pulls newer files from the secondary on demand.
~/.claude/— skills, plugins, MCP configs, settings, memory, project CLAUDE.md files. Excludes conversation transcripts, debug logs, telemetry, and caches (~4 GB saved per sync).~/.claude/history.jsonl— merged (not overwritten) by timestamp with dedup, so slash-command history from both machines survives.~/.agents/— shared skill sources.~/Library/Application Support/Claude/— Claude Desktop config JSON files only. Electron runtime caches, VMs, and session databases are skipped (~9 GB saved).~/.docker/mcp/— Docker MCP server configs (Atlassian, Playwright, etc.).mcp-toolkit.dbis skipped.~/Development/— all projects. Seesync-exclude.txtfor what's skipped (node_modules/,.next/,.turbo/,dist/, etc.).~/.zshrc,~/.gitconfig— shell config, git identity.~/Library/LaunchAgents/com.*.env.plist— GUI app environment variables.- Optional:
~/Documents/Obsidian/<vault>/Uploads/— large files outside the paid Obsidian Sync service, via a Studio-initiated SSH push that bypasses macOS TCC restrictions on writes to~/Documents/. Disabled by default; see "Optional: Obsidian Uploads" below.
On both Macs: enable Remote Login (System Settings → General → Sharing → Remote Login).
git clone <this-repo> ~/Development/macos-sync
cd ~/Development/macos-sync
# Edit the top of sync-from-studio.sh to set:
# STUDIO_HOST — your SSH alias for the primary Mac
# STUDIO_USER — your macOS username on the primary Mac
# HOME_NETWORK_PREFIX — subnet the sync is allowed to run on
# Then:
./setup-macbook.shsetup-macbook.sh generates an SSH key, appends an entry to ~/.ssh/config, registers the key with the primary, installs the LaunchAgent, and runs an initial sync.
If you want to pull newer secondary-Mac files to the primary manually when you sit back down:
cd ~/Development/macos-sync
# Edit the top of pull-from-macbook.sh to set MACBOOK_HOST and HOME_NETWORK_PREFIX.
./setup-studio.shsetup-studio.sh discovers the secondary via Bonjour (or prompts), sets up a reverse SSH key, and registers it.
Trigger a reverse pull with:
~/Development/macos-sync/pull-from-macbook.shRuns on login, on network change (watched via /Library/Preferences/SystemConfiguration), and hourly as a fallback. Throttled to at most once per 5 minutes so DHCP renews and WiFi roams don't spam it.
Each run:
- Preflight. Lock file prevents overlap; skip unless on the configured home network; skip silently if the primary is unreachable.
- Reverse push (safety net). Any newer secondary-Mac files under
~/Development/and~/.claude/skills/get pushed to the primary with--update(no--delete), so the main pull can't clobber laptop-only work. - History merge.
~/.claude/history.jsonlis fetched from the primary, deduplicated, sorted by timestamp, and written back as the union of both machines' entries. - Main pull. All the paths listed above, with
--archive --compress --delete --partial --itemize-changes. - Notify. macOS notification on changes or errors; silent on clean no-op runs. Full log at
~/Library/Logs/studio-sync.log.
Uses --update and no --delete — only copies files that are newer on the secondary, never removes files from the primary. Runs the same history.jsonl merge logic. Safe to run anytime; skips if the secondary isn't reachable.
A block in sync-from-studio.sh (and a counterpart in pull-from-macbook.sh) can sync a folder from an Obsidian vault that's too large for the paid Obsidian Sync service — or you can adapt it to sync an entire vault. It's disabled by default in the public build, wrapped in a : <<'OBSIDIAN_DISABLED' ... OBSIDIAN_DISABLED heredoc.
macOS TCC blocks LaunchAgent-spawned processes from writing to ~/Documents/, even with Full Disk Access granted to a wrapper .app. The workaround in the code: the secondary SSHes into the primary and tells the primary to rsync to the secondary. Files arrive via sshd, which writes with the user's full permissions and bypasses TCC entirely.
To enable:
- Set
UPLOADS_PATHinsync-from-studio.shto your vault's folder (relative to$HOME). - Remove the
: <<'OBSIDIAN_DISABLED'and matchingOBSIDIAN_DISABLEDlines that bracket each block.
A helper (safe_sync_sqlite) exists to snapshot SQLite DBs via sqlite3 .backup before rsync, since .db + .db-wal + .db-shm must be copied atomically. Currently unused — claude-mem sync was removed after WAL/shm mismatches kept corrupting the DB and breaking Claude Code on the secondary Mac. Each machine now keeps its own ~/.claude-mem/ copy.
~/Development/macos-sync/sync-from-studio.shUpgrading from an earlier version of this script? You may have accumulated Claude Desktop caches and Claude Code session transcripts that are now excluded from sync but still sitting on disk (multi-GB). Run once:
~/Development/macos-sync/cleanup-stale-sync.shsync-exclude.txt controls what's skipped under ~/Development/. Add projects, build caches, or anything else you don't want on the secondary.
- Forward sync log:
~/Library/Logs/studio-sync.log(prefix:START,REVERSE,OK,FAIL,DONE) - Reverse pull log: same file, prefix
*-REV
All user-specific values live at the top of each script — edit once, then commit if you're tracking your own fork:
| Variable | File | Example |
|---|---|---|
STUDIO_HOST |
sync-from-studio.sh |
SSH alias for the primary Mac |
STUDIO_USER |
sync-from-studio.sh, setup-macbook.sh |
macOS username on the primary Mac |
MACBOOK_HOST |
pull-from-macbook.sh |
SSH alias for the secondary Mac |
HOME_NETWORK_PREFIX |
both sync scripts | e.g. 192.168.1 |
UPLOADS_PATH |
sync-from-studio.sh |
path to your Obsidian Uploads folder, relative to $HOME (only if you enable the optional Obsidian block) |
Label, plist paths |
com.*.studio-sync.plist |
reverse-DNS label; edit StandardOutPath/StandardErrorPath to your username |
BSL 1.1 — see LICENSE. Converts to Apache 2.0 on 2030-04-23.