# claude-self-sync:用兩個 GitHub repo 同步多台機器的 ~/.claude
TL;DR
我寫了一個 bash 腳本claude-self-sync,把~/.claude拆成「公開 config」+「私密 secrets」兩個 git repo,三條指令push/pull/status解決 Claude Code 多機同步。Dropbox / iCloud 的雷我替你踩過了,所以這個工具預設打開了六道防線。
如果你也是同時用兩台以上電腦在跑 Claude Code 的人——例如家裡 Mac mini + 公司筆電,或常常拉桌機跟筆電換工位——那 ~/.claude 這個資料夾的同步就會變成你日常隱性焦慮的來源。skills 加在 A 機,B 機看不到;memory 在 B 機更新,A 機過幾天忘了改了什麼;某天某個 hook 突然失效,根本不知道是哪一台動到的。
我之前用 Dropbox 同步,結果出事了。所以重新寫了一套。
目錄
為什麼不用 Dropbox / iCloud
我一開始也是直接把 ~/.claude 丟進 Dropbox。理由很單純:「同步資料夾不就 Dropbox 嗎?」
結果踩到三個雷:
雷 1:雙寫衝突沒救。 Dropbox 兩邊同時改同一個 MEMORY.md,它會默默幫你建 MEMORY (conflict copy from BobAir 2026-04-12).md。等你發現的時候,已經有 17 個 conflict copy 散在 memory 資料夾裡,而你用了某幾天的 MEMORY.md 已經是錯的版本。
雷 2:HNSW 向量索引炸了。 我有裝 mempalace(Chroma 向量庫),它的 segment 是綁機器的 native 二進位。Dropbox 把 A 機寫到一半的 segment 同步到 B 機,B 機開 mempalace,segfault。
雷 3:沒有「我反悔了」按鈕。 Dropbox 是 filesystem-level sync,沒有 commit、沒有歷史、沒有 diff。你誤刪一個 skill,它三秒內傳染到所有機器,等你發現已經沒得救(除非翻 Dropbox 三十天垃圾桶,但那是 web UI,路徑跟一堆 random ID)。
git-based 全部解決: commit 是顯式動作(你要主動 push 才動)、有完整歷史可 revert、有 diff 可看、可以選擇性同步、可以兩個 repo 拆開(公開 + 私密)。
兩個 repo 的拆法(為何要拆)
這是 claude-self-sync 跟其他單 repo 方案最大的設計差別。
| Repo | Visibility | 內容 |
|---|---|---|
claude-config | public(或 private 你決定) | skills/ agents/ commands/ rules/ scripts/ hooks/hooks.json |
claude-secrets | 必須 private | CLAUDE.md settings.json memory/ agent-memory/ services/ |
- skills / agents / commands 是「人類可寫的純配置」——別人看了能用,你也可能想開源(像我這個工具本身)。放 public repo,方便分享、方便 GitHub search 收錄。
- memory / CLAUDE.md / settings 是「Claude 跟你協作的歷史」——裡面有客戶名稱、API key 殘片、私事、判斷紀錄。絕對不能 public。
順帶一提,claude-self-sync 本身就是從 claude-config 裡的某個 script 抽出來開源的——這個 split 設計實際上就是讓我能做這件事的前提。
三條指令搞定全部
sync.sh push # local → GitHub(commit + push 兩個 repo)
sync.sh pull # GitHub → local(有 delete 預掃保護)
sync.sh pull --force # 跳過 delete 預掃(你確認可以刪)
sync.sh status # 比對 local vs repos,不寫入
sync.sh push --dry-run # 預覽 push,不寫入
sync.sh pull --dry-run # 預覽 pull,不寫入
實際使用節奏:
- 每天早上開機第一件事:
sync.sh pull(拉昨天另一台機器的更新) - 每天工作收尾:
sync.sh push(推今天的改動) - 不確定要不要 push 時:
sync.sh status看 diff - 跨機器測試 hook 時:
sync.sh pull --dry-run看會動到什麼
安全機制:六道防線
這不是 cp -r ~/.claude/* ~/repo。每一個防線都對應一個我曾經踩過的真實事件。
防線 1:Push 用 add-only rsync(沒有 --delete)
本機誤刪一個檔案不會傳染到 repo。如果你想刪 cross-machine,請去 repo 裡刪,再 pull 下來。
踩坑來源: 某次清理本機亂檔案,連同某個 hook 一起刪了,push 後 B 機 pull 下來才發現 hook 沒了,已經 commit 在 repo。要 revert 還得回去翻歷史。
防線 2:Pull 前 delete 預掃 + --force 機制
pull 預設用 --delete(mirror 模式),但會先掃一遍:「以下檔案會被刪掉:[列表]」。如果有任何刪除,強制要求 --force 才繼續。
踩坑來源: 某天 pull 之前忘記 push,結果 mirror 模式把今天剛寫好還沒 push 的 sync.sh、3 個 memory 檔、1 個新 skill 全部刪掉。從那天起這個預掃就成了 default。
防線 3:Pull 前自動 tar.gz 備份
~/.claude/backups/sync-pull-{timestamp}.tar.gz,保留最近 3 份。即使 --force 出事,至少有得救。
防線 4:Push 前 git fetch 檢查
如果 repo 落後 remote(代表另一台機器先 push 過),拒絕 push。逼你先 pull 合併再 push。避免你覆蓋掉另一台機器的 commit。
防線 5:mkdir-based mutex
/tmp/claude-self-sync.lock.d 用 mkdir 原子操作做鎖。同一台機器不會雙跑。macOS 沒有 flock,所以用 mkdir。
防線 6:openrsync 噪音過濾
macOS 內建的 openrsync(不是 GNU rsync)偶爾會噴 unlinkat: Directory not empty,但實際傳輸沒問題。rsync_quiet() wrapper 把這類已知非致命警告吃掉,避免你看到 stderr 就以為失敗。
為什麼有些東西「故意」不同步
這是「safety-first」哲學的另一面:有些東西就是不該跨機器同步。
| 排除 | 原因 |
|---|---|
~/.mempalace/(Chroma 向量庫) | HNSW segment 是 single-machine 的 native binary,跨機 sync 會炸索引 |
.mcp.json | 含絕對路徑(每台機器不同),只同步 .mcp.json.example 範本 |
巢狀 .git/ | 會把 repo size 撐爆,git 也會搞混 |
__pycache__/ *.pyc | 本機 Python bytecode,沒意義 |
.DS_Store | macOS Finder metadata,純垃圾 |
node_modules/ .venv/ venv/ | 本機重裝就好,不要塞進 repo |
services 的 .env 和 *.log | 本機密碼跟 log noise |
安裝 5 步驟
# 1. 抓腳本
curl -fsSL https://raw.githubusercontent.com/yanchen184/claude-self-sync/main/sync.sh \
-o ~/.claude/scripts/sync.sh
chmod +x ~/.claude/scripts/sync.sh
# 2. 在 GitHub 開兩個空 repo(不要 init README)
# https://github.com/new → claude-config(public 或 private)
# https://github.com/new → claude-secrets(必須 private!)
# 3. clone 到家目錄
cd ~ && git clone git@github.com:<你的帳號>/claude-config.git
cd ~ && git clone git@github.com:<你的帳號>/claude-secrets.git
# 4. 從你的「source of truth」機器第一次 push
~/.claude/scripts/sync.sh push
# 5. 在另一台機器 pull
~/.claude/scripts/sync.sh pull
如果 step 5 跑 pull 它會說「以下檔案會被刪掉」,那是因為 B 機有 A 機沒有的本機檔案。兩個選擇:
- 在 B 機先
push(把 B 的東西先送上去) - 確認那些檔案可以丟,跑
pull --force
配 Claude Code slash command(選用)
寫 ~/.claude/commands/sync.md:
# Sync Claude Config
Run ~/.claude/scripts/sync.sh $ARGUMENTS and report the result.
- 空 / "pull" →
pull - "push" →
push - "status" →
status - 帶
--dry-run 或 --force 也轉發
之後在 Claude Code 裡 /sync push、/sync pull 就能用。
跟其他方案的差別
| 方案 | 機制 | 缺點 |
|---|---|---|
| Peter-Moriarty/claude-code-multi-machine-setup | Dropbox / iCloud filesystem sync | 雙寫衝突、沒 delete 保護、沒 quality gate |
| Plugin marketplace | 集中式 skill 分發 | 給多人用的,不是給自己多裝置 |
| stow / chezmoi | 泛用 dotfile manager | 不懂 Claude Code 結構(skills vs memory vs agent-memory),不會自動拆公開 / 私密 |
手動 cp -r | 你自己記得跑 | 不會記得;會搞混版本;沒有 history |
| claude-self-sync | git + 兩 repo 拆 + 預掃保護 | macOS / Linux only;要會用 git |
- ❌ 沒有 GUI、沒有背景 daemon、沒有 cloud service
- ❌ 不處理 secret rotation、不加密(GitHub private repo 已經夠 personal use)
- ❌ 不支援 Windows(沒測過,bash 應該大致 work)
常見問題 FAQ
Q1:可以多台機器同時 push 嗎?
不行。Push 前的 git fetch 檢查會擋掉「落後 remote」的 push。手動 pull 合併再 push。雙寫機率本來就低,不值得寫 auto-merge 邏輯。
Q2:私密 repo 真的安全嗎?
GitHub private repo 跟你其他私密 repo 一樣安全。前提是你沒不小心開成 public。第一次 push 之前去 repo settings 確認 Visibility: Private 一次。
Q3:mempalace(向量記憶)為什麼不能 sync?
向量索引(HNSW)的 segment 是 native binary,跟編譯目標機器綁定。A 機寫到一半的 segment 同步到 B 機 → B 機 mempalace 開起來 segfault。同步 chunk metadata 可以,但 native segments 不行。設計上就排除整個 ~/.mempalace/。
Q4:pull --force 會把我本機改的東西全部弄掉嗎?
會。所以才預設要 --force。請務必先看 dry-run 列出要刪的檔案清單,確認那些是你不要的(例如測試殘骸、已經改名的舊檔)才下手。每次 --force 之前 tar.gz 備份還是會跑,最壞還可以解 tar 救回。
Q5:可以用在 Linux 嗎?
sync.sh 本身在 Linux 該 work(rsync、bash、git 都有)。launchd plist 那段是 macOS-only,會自動 skip。
Q6:A 機刪了某個 skill,怎麼讓 B 機也跟著刪?push 預設 add-only 不會傳染刪除。正確做法:去 claude-config repo 裡刪那個檔案、commit 到 GitHub,B 機 pull 下來時 delete 預掃會列出它,確認後 pull --force。多兩步,但這是設計就是要逼你「故意」才能跨機刪除。
延伸資源
- 🔗 GitHub repo — 主 repo + INSTALL.md
- 🔗 Claude Code 官方文件 — 了解
~/.claude目錄結構 - 🔗 openrsync 跟 GNU rsync 的差別 — macOS 預設 rsync 的細節
- 🔗 mkdir-based locking 的原理 — 為何 mkdir 是好的 mutex
- 🔗 我的下一篇文章:從 271 行 markdown 重寫成開源工具的 6 個 bash 踩坑 — 開發實錄