用了两台 Mac 跑 Claude Code 之后,配置漂移是必然的:主力机上的 CLAUDE.md 改了十几版,rules 目录加了新规则,另一台机器还停留在几个月前的状态。这次把同步这件事做完整,记录一下边界怎么划、踩了哪些坑。
同步范围:~/.claude 不能整个搬
第一反应可能是 rsync -a ~/.claude/ remote:~/.claude/ 一把梭。不行。这个目录里配置和状态混在一起,先看清里面有什么:
ls ~/.claude/# CLAUDE.md settings.json rules/ skills/ scripts/ plugins/ ← 配置,该同步# .credentials.json history.jsonl projects/ sessions/ tasks/ ← 状态/凭据,绝不同步# file-history/ shell-snapshots/ session-env/ paste-cache/ ← 会话产物,同步了纯属污染划分标准就一条:这个文件描述”我希望 Claude 怎么工作”,还是记录”Claude 在这台机器上干过什么”? 前者同步,后者留在本机。
最终的同步集:
CLAUDE.md、settings.json—— 全局指令和设置rules/、skills/、scripts/—— 规则、技能、hook 引用的脚本plugins/的配置 json +marketplaces/+cache/—— 插件要能在对面机器直接加载
绝对不碰的:.credentials.json(凭据,每台机器各自登录)、projects/(含 memory 和会话历史)、history.jsonl、sessions/、tasks/。社区的通行做法也是只同步 CLAUDE.md / settings / agents / skills / rules 这一层,凭据类每台机器重新认证。
settings.json 同步前先看一眼 env 段
settings.json 里有个 env 字段,有人会往里塞 API key。同步前用 jq 只看键名、不看值:
jq '.env | keys' ~/.claude/settings.json# ["CLAUDE_CODE_DISABLE_TERMINAL_TITLE", "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS", ...]确认全是无害开关才能同步。如果里面有密钥,要么挪去 shell 环境变量,要么这个文件就不能进同步集。
另一个容易忽略的:settings.json 里的 hooks 和 statusLine 经常引用绝对路径。我的配置里就有两处:
jq -r '.statusLine, (.hooks | tostring)' ~/.claude/settings.json | grep -oE '/Users/[^" ]+'# /Users/user/.open-island/bin/open-island-statuslinesettings 同步过去了,这些脚本没跟着过去,hook 就会静默失败。所以依赖也要进同步集——scripts/ 目录、statusline 脚本、插件市场引用的本地目录,一个都不能少。同步完在远端实际执行一次验证:
ssh remote 'echo "{}" | ~/.open-island/bin/open-island-statusline'# [Claude] 0% context ← 能跑SSOT 不等于无脑镜像
用户视角的规则是”本地是 single source of truth”。但落到 rsync 参数上要分两种情况:
rules/用--delete镜像——规则就该完全一致;skills/不带--delete——先对比两边目录,发现远端有几个本机独有的 skill(对方机器上自己长出来的工作流文档)。同名的以本地覆盖,独有的保留。
无脑 --delete 会把对方机器积累的本地产物直接删没。SSOT 解决的是冲突时听谁的,不是对方多出来的东西该不该存在。
覆盖前照例打包备份,出问题能整体回滚:
ssh remote 'tar czf ~/.claude/backups/config-sync-bak-$(date +%Y%m%d%H%M%S).tar.gz \ -C ~ .claude/settings.json .claude/CLAUDE.md .claude/rules .claude/skills'还有一个并发细节:动手前发现远端的 settings.json 几分钟前刚被改过——那台机器上有个活跃的 Claude Code session 也在写配置。这种情况备份就不只是仪式了。同步过去的新配置对已在跑的 session 不生效,只影响新启动的。
环境清单跨机误导:每台机器自己描述自己
我的 rules/ 里有一份 cli-tools.md——本机装了哪些 CLI 工具的清单,让 Claude 不用每次现探测。镜像同步之后马上意识到一个问题:这份清单描述的是主力机,现在躺在另一台机器的 rules 里。对面那台是极简环境,清单里的 rg / fd / pandoc / psql 一个都没有。Claude 在那台机器上读到这份清单,会理直气壮地调用不存在的工具。
解法:清单不同步,每台机器实扫生成自己的版本。扫的时候先踩了个经典坑——
ssh remote 'brew list --formula | wc -l'# 0 ← 看起来没装 brew?ssh remote 'claude --version'# command not found ← 看起来没装 claude?都是假的。非交互 SSH 不加载交互 shell 的 PATH,/opt/homebrew/bin 和 ~/.local/bin 都不在里面。补上 PATH 再扫,90 个 formulae 好好地在那:
ssh remote 'export PATH="/opt/homebrew/bin:$HOME/.local/bin:$PATH"; brew list --formula | wc -l'# 90这个教训直接写进了那台机器的清单开头:探测前先补 PATH。顺带发现一个冷知识:那台机器上 jq 在 /usr/bin/jq——从 macOS Sequoia 起系统就自带 jq 了,不再需要 brew 装。
两台机器的清单最后长成了不同的样子:主力机版本详尽罗列十几个分类;极简机版本开头是一段”与主力机的关键差异”速查——没有 conda/nvm/rbenv、没有容器工具、常用文本工具基本缺席——先告诉 Claude 不能假设什么,比罗列有什么更防错。
顺带把主力机的清单重扫了一遍
清单自带的快照日期是两个半月前。重扫发现漂移比预期大得多:
- miniconda 整个没了——zshrc 里的 conda shim 还在,但
~/miniconda3已删除,原 conda base 里的整套 ML 栈跟着蒸发,Jupyter 一族现在由 brew 提供。如果不重扫,Claude 会继续以为conda activate可用; - brew formulae 从 191 涨到 232;
- LM Studio、Antigravity 卸了,VS Code、yt-dlp、一套嵌入式工具链(esptool/platformio)装上了。
这类环境清单文件的正确姿势是开头写死一行”快照日期 + drift expected + 用前 command -v 复核”,并且在文末附上”怎么正确重扫”的步骤——这次重扫就是直接照着清单自己写的步骤执行的,包括用 zsh -ic 探测 lazy-loaded 的 nvm/conda 这种细节。文档教会未来的自己(和未来的 Claude)怎么更新它自己,这个闭环比文档内容本身更值钱。
总结
~/.claude是配置和状态的混合体,同步前先分类:描述”怎么工作”的同步,记录”干过什么”的留下,凭据永远不碰;- settings.json 同步 = settings.json + 它引用的所有绝对路径依赖,缺一个 hook 就静默失败,同步完要在远端实测;
- SSOT 管冲突仲裁,不管存在性——对方机器独有的产物保留,别用
--delete一刀切; - 描述单台机器事实的文件(环境清单)不进同步集,每台机器实扫自描述;
- 非交互 SSH 的 PATH 不含 brew 和用户 bin,跨机探测”command not found”先怀疑 PATH 再怀疑没装;
- 快照类文档要自带生成方法和过期声明,否则过期比没有更糟。