第 31 章 备份与迁移
换 Mac、重装系统、给团队克隆一套环境——这些场景的共同痛点是:Claude Code 用了半年积累的配置、记忆、对话一旦丢失,重建成本远高于代码本身。本章给出一套已经落地验证的全量备份与迁移方案,核心是分清三类资产、避开
.claude.json整体覆盖这个最大陷阱、凭据永远不迁移。素材来自 2026-06-30 的一次真实换机迁移,恢复脚本已bash -n校验并在新机跑通。
31.1 结论:备份本质是保护三件资产
Claude Code 的备份与迁移,本质是保护三件资产:
- 配置资产——
~/.claude/目录(settings.json、agents/、skills/、commands/、output-styles/、keybindings/、plugins/)与~/.claude.json(MCP 服务器定义、设备身份字段) - 上下文资产——全局
~/.claude/CLAUDE.md、项目级CLAUDE.md、~/.claude/projects/下的 memory 文件与会话历史 jsonl - 对话资产——
~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl的全部对话记录(主会话 + 子代理会话)
换 Mac 的默认方案是全量备份 + 分层恢复:把三件资产打成 tar.gz,在新机上分层复制配置、合并(而非覆盖).claude.json、凭据重新登录。这条路最直接、最安全、可回滚。
为什么不全靠云同步(iCloud/Dropbox)把 ~/.claude/ 整个同步过去?因为 Claude Code 的资产里混着三类不能无脑同步的东西:
- OAuth 凭据——设备绑定,同步过去会失效,且可能泄露
- 运行时缓存——
cache/、shell-snapshots/、telemetry/、sessions/含旧机绝对路径,同步过去会污染新机 .claude.json身份字段——userID、machineID整体覆盖会污染新设备统计、导致 OAuth 错乱
所以备份必须是本地打包 + 选择性恢复,不是无脑 rsync 整个 ~/.claude/。这是本章与"把整个家目录丢上云"的根本区别。
31.2 资产盘点:要迁什么、不迁什么
迁移前先做资产分类。下表是 2026-06-30 真实换机时的清单,直接复用:
| 资产 | 路径 | 策略 | 说明 |
|---|---|---|---|
| 全局指令 | ~/.claude/CLAUDE.md | 必迁 | 用户全局规范,丢失等于丢工程纪律 |
| 全局设置 | ~/.claude/settings.json | 必迁 | 权限规则、hooks、env、模型默认值 |
| 自定义 skills | ~/.claude/skills/ | 必迁 | 自建 skill,丢了要重写 |
| 自定义 agents | ~/.claude/agents/ | 必迁 | subagent 定义 |
| 自定义 commands | ~/.claude/commands/ | 必迁 | 自建 slash command |
| output-styles | ~/.claude/output-styles/ | 必迁 | 输出风格 |
| keybindings | ~/.claude/keybindings.json | 必迁 | 键位绑定 |
| 插件 | ~/.claude/plugins/ | 必迁 | 已安装插件 |
| 记忆 | ~/.claude/projects/<proj>/memory/ | 必迁 | auto-memory,核心知识资产 |
| 会话历史 | ~/.claude/projects/<proj>/*.jsonl | 必迁 | 对话原始数据 |
| 计划文件 | ~/.claude/plans/ | 必迁 | Plan Mode 产出 |
| 命令历史 | ~/.claude/history.jsonl | 必迁 | 上下文回溯 |
| MCP 服务器 | ~/.claude.json → mcpServers | 字段级合并 | 只搬 mcpServers,不动身份字段 |
| OAuth 凭据 | ~/.claude.json → oauthAccount | 不迁 | 新机重新 /login |
| 设备身份 | ~/.claude.json → userID/machineID | 不迁 | 保留新机原值 |
| 运行时缓存 | ~/.claude/{cache,shell-snapshots,telemetry,sessions} | 不迁 | 含旧机绝对路径,有害无益 |
| 代码仓库 | ~/claude/* 下的项目代码 | 单独迁 | 走 git/rsync,不属于 CC 资产 |
这张表是本章的承重墙。后面所有命令都在执行这张表的决策:必迁的打包,字段级合并的用 jq 抽取,不迁的排除,单独迁的走 git。
31.3 备份目录约定与全量备份命令
备份位置
约定备份统一放在 ~/claude/_backups/。为什么不是 ~/.claude/_backups/?因为 ~/.claude/ 是 Claude Code 自己的运行时目录,把备份塞进去会污染大本营根、干扰工具扫描、还可能被纳入下一次备份形成递归。~/claude/ 是你的工作区根目录,与 CC 运行时物理隔离,备份放这里干净。
命名约定:claude-full-backup-YYYYMMDD.tar.gz,滚动保留最近 3 份,旧的自动删除。
全量备份命令
# 全量备份(滚动保留最近 3 份)
mkdir -p ~/claude/_backups
cd ~ && tar czf ~/claude/_backups/claude-full-backup-$(date +%Y%m%d).tar.gz \
--exclude='*/.DS_Store' \
.claude/CLAUDE.md \
.claude/settings.json \
.claude/skills \
.claude/agents \
.claude/commands \
.claude/output-styles \
.claude/keybindings.json \
.claude/plugins \
.claude/projects \
.claude/plans \
.claude/history.jsonl \
.claude.json
# 滚动清理:只保留最近 3 份
ls -t ~/claude/_backups/claude-full-backup-*.tar.gz | tail -n +4 | xargs -r rm
# 查看备份内容确认
tar tzf ~/claude/_backups/claude-full-backup-$(date +%Y%m%d).tar.gz | head -20预期输出:
claude-full-backup-20260708.tar.gz 约 40M(规模因记忆/会话量而异)
# tar tzf 输出前几行
.claude/CLAUDE.md
.claude/settings.json
.claude/skills/
.claude/skills/port-debug/SKILL.md
...验证:
# 备份大小与条目数
ls -lh ~/claude/_backups/claude-full-backup-*.tar.gz
tar tzf ~/claude/_backups/claude-full-backup-$(date +%Y%m%d).tar.gz | wc -l
# 应输出 2000+ 条目(视 skills/plugins/记忆数量)失败边界:
- 备份文件超过 500M——大概率是把
~/claude/代码仓库误打包进去了,检查 tar 参数是否误含~/claude/* - 备份里出现
cache/或shell-snapshots/——排除规则写错,运行时缓存不该进备份 tar: No such file报错——某个目录不存在(比如没装过 plugins),用$(test -d ~/.claude/plugins && echo .claude/plugins)条件追加
定期自动备份(可选)
# 加到 crontab -e,每周日凌晨 3:17 执行
17 3 * * 0 cd ~ && tar czf ~/claude/_backups/claude-full-backup-$(date +\%Y\%m\%d).tar.gz --exclude='*/.DS_Store' .claude/CLAUDE.md .claude/settings.json .claude/skills .claude/agents .claude/commands .claude/output-styles .claude/keybindings.json .claude/plugins .claude/projects .claude/plans .claude/history.jsonl .claude.json && ls -t ~/claude/_backups/claude-full-backup-*.tar.gz | tail -n +4 | xargs -r rm注意 crontab 里 % 必须转义为 \%,否则 cron 会把 % 后当 stdin 截断。
31.4 迁移到新 Mac:六步分层恢复
安装:用官方 native,不用 cmux
新机第一件事是装 Claude Code 本体。用官方 native 安装(2026-07-08 核对,以 code.claude.com/docs 为准):
curl -fsSL https://claude.ai/install.sh | bash
claude --version不要用 cmux 或其他第三方封装。原因:第三方封装的版本更新滞后、凭据链路不可控、出问题时排查成本高。官方 native 的版本、签名、更新机制都由 Anthropic 直接管理。本机历史遗留过 cmux + native 双轨,迁移时正好切回单 native。
六步分层恢复
把 _backups/ 整个目录拷到新机(U 盘 / 隔空投送 / rsync),然后在 _backups/ 目录下执行恢复脚本 restore-on-new-mac.sh。脚本做六件事:
| 步骤 | 动作 | 破坏性 |
|---|---|---|
| 0 | 检查 jq 是否安装、定位备份包 | 否 |
| 1 | 未装则装官方 native | 否 |
| 2 | 确认已 claude 登录(OAuth,手动) | 否 |
| 3 | 解压备份到 ~/restore-cc 临时目录 | 否 |
| 4 | 分层复制配置/记忆/历史/skills/plugins/agents/commands | 否(只新增) |
| 5 | jq 合并 mcpServers 到 ~/.claude.json,自动备份原文件 | 否(有 .bak) |
| 6 | 验证版本/记忆加载/MCP 连通/skills 可用 | 否 |
整个流程非破坏性:只复制用户资产 + 合并配置,新机原有数据不动。第 5 步是唯一需要警惕的环节,下一节单独讲。
验证(恢复后逐项):
claude --version # 版本正常输出
ls ~/.claude/CLAUDE.md # 全局指令存在
ls ~/.claude/skills/ # 自定义 skill 在
ls ~/.claude/agents/ # 自定义 agent 在
ls ~/.claude/projects/-Users-*/memory/MEMORY.md # 记忆索引在然后启动 claude,在交互会话里验证:
- 输入
/memory,确认MEMORY.md内容出现在上下文(记忆加载成功) - 输入
/plugin,确认原插件可见且可启用 - 输入
/mcp,确认 MCP 服务器web-search-prime等 connected - 调用一个自定义 skill(如
/port-debug)确认可用
失败边界:
/memory看不到内容——projects/<encoded-cwd>/memory/的 encoded 路径与新机 cwd 不匹配,记忆按项目路径索引,换机后路径编码可能变(下节详解)/mcp显示 MCP failed to connect——.claude.json合并失败或 MCP 服务器密钥缺失(有些 MCP 配置内联密钥,迁移后需补)- skill 调用报 command not found——
~/.claude/skills/<name>/SKILL.md的 frontmatter 格式错误或路径层级不对
31.5 .claude.json 字段级合并:最大的迁移陷阱
为什么不能整体覆盖
~/.claude.json 是一个混合文件,既有用户配置、又有设备身份、还可能内联 MCP 密钥。整体覆盖会带来三重灾难:
- 身份污染——带入旧机的
userID、machineID,新设备统计错乱,Anthropic 后台看到一台"幽灵设备" - OAuth 错乱——旧机的
oauthAccounttoken 是设备绑定的,复制过去既失效又可能触发安全告警 - 配置丢失——新机如果在安装后已经生成了自己的
.claude.json(比如装了别的 MCP),整体覆盖会丢新机已有配置
正确做法:只合并 mcpServers 字段,其余字段保留新机原值。
jq 合并命令
# 1. 从备份的 .claude.json 提取 mcpServers
jq '.mcpServers' ~/restore-cc/.claude.json > /tmp/old-mcp.json
# 2. 备份新机当前 .claude.json
cp ~/.claude.json ~/.claude.json.bak.$(date +%Y%m%d%H%M%S)
# 3. 合并:新机 mcpServers + 旧机 mcpServers(旧机覆盖同名)
jq --slurpfile old /tmp/old-mcp.json \
'.mcpServers = (.mcpServers + $old[0])' \
~/.claude.json > ~/.claude.json.tmp \
&& mv ~/.claude.json.tmp ~/.claude.json
# 4. 验证合并结果
jq '.mcpServers | keys' ~/.claude.json预期输出:
# jq '.mcpServers | keys' 输出
[
"web-search-prime",
"fetch",
"other-mcp"
]验证:
# 启动 claude 后在会话内执行
/mcp
# 应看到 web-search-prime 等服务器状态为 connected失败边界:
jq: error: Cannot index null——某一方.claude.json里没有mcpServers字段(全新安装),用// {}兜底:.mcpServers = ((.mcpServers // {}) + ($old[0] // {}))- MCP 服务器显示 connected 但调用报 401——该 MCP 配置里内联了 API key,而 key 没迁过来(出于安全,密钥不应进备份包);需在新机手动补 key 或用环境变量注入
- 合并后
.claude.json变大且含敏感字段——确认.gitignore已忽略,绝不进 git
安全红线
.claude.json 当密钥文件对待:
- 绝不提交到 git(公开仓库泄露 = 身份 + 可能的 MCP 密钥全曝光)
- 备份包本身加密或限定权限:
chmod 600 ~/claude/_backups/claude-full-backup-*.tar.gz - 跨机传输用加密渠道,不用公开网盘
31.6 凭据处理:新机重新登录
OAuth token 永远不迁移。原因:
- 设备绑定——Anthropic 的 OAuth token 与设备指纹关联,复制到新机直接失效
- 泄露风险——token 进备份包,备份包流转过程中可能泄露
- 安全审计——新机登录会在 Anthropic 后台生成新会话记录,便于审计;迁移旧 token 会绕过这个记录
正确做法:新机装完 native 后,首次运行 claude 会触发 OAuth 流程,浏览器跳转完成登录。这样新机拿到自己的 token,与设备绑定,干净。
# 新机首次启动
claude
# 浏览器跳转 → 完成登录 → 回到终端看到欢迎信息验证:
# 会话内执行
/status
# 应显示已登录账户、订阅状态、token 有效失败边界:
- 登录后
claude命令仍报未认证——检查~/.claude.json是否被旧备份覆盖(整体覆盖陷阱),回滚到.bak文件 - 双重订阅告警——旧机未
/logout就迁,新机登录后旧机 token 仍有效;在旧机执行/logout或在 Anthropic 后台 revoke 旧会话
31.7 对话历史导出:export_conversations_md.py
为什么需要单独导出
~/.claude/projects/ 下的 jsonl 是原始对话数据,体积大(158MB)、可读性差(每行一条 JSON)。归档时需要一份人类可读的 Markdown 版本。export_conversations_md.py 脚本完成这个转换。
脚本位置:/Users/mba/claude/_backups/export_conversations_md.py(纯标准库,可重跑增量覆盖)
导出位置:~/claude/_backups/conversations-md/<项目>/<日期>_<标题或stem>.md
导出命令
python3 /Users/mba/claude/_backups/export_conversations_md.py预期输出:
# 扫描 ~/.claude/projects/ 下所有 jsonl
# 主会话(用户↔Claude 直接对话): 各项目顶层,37 个
# 子代理/workflow 会话: <项目>/<sessionId>/subagents/ 深层,623 个
# 合计 660 个 MD,21MB(源 jsonl 158MB,折叠+截断+去 base64 后大幅缩减)MD 格式:会话元信息(会话 ID/时间/cwd/消息数)+ 按时序的 user/assistant 消息;thinking/tool_use/tool_result 用 <details> 折叠;tool_result 超 1500 字截断。
关键坑:子代理 sessionId 撞名
这是踩过的最大坑:
错误做法:用 message.sessionId 作文件名。子代理的 sessionId 继承父会话,所以 623 个子代理会话的 sessionId 都等于父会话 id,导致 623 个文件覆盖成 37 个,大量对话丢失。
正确做法:用 jsonl 文件 stem(文件名去掉 .jsonl)作唯一兜底。每个 jsonl 文件名本身就是唯一的会话标识,用它做文件名不会撞。
# 错误(第一版,丢数据)
filename = f"{message.sessionId}.md"
# 正确(修复后)
filename = f"{date}_{Path(jsonl_path).stem}.md"另一个坑:子代理 jsonl 不在项目顶层,而在 <项目>/<sid>/subagents/ 深层。必须用 glob('**/*.jsonl', recursive=True) 递归扫描,否则只导出主会话,漏掉全部子代理记录。
验证:
# 主会话数量
find ~/claude/_backups/conversations-md -maxdepth 2 -name "*.md" | wc -l
# 子代理会话数量
find ~/claude/_backups/conversations-md -path "*/subagents/*" -name "*.md" | wc -l
# 抽查一个文件内容
head -30 ~/claude/_backups/conversations-md/<项目>/<某个>.md失败边界:
- 导出文件数远少于 jsonl 数——大概率是 sessionId 撞名覆盖,检查是否用文件 stem
- 子代理会话全部缺失——
glob没加recursive=True - MD 里出现大段 base64 乱码——图片附件没过滤,在解析 attachment 时跳过
data:image开头的内容 python-docx读不到对话——jsonl 不是 docx,直接json.loads逐行解析
31.8 迁移验证清单
恢复完成后,逐项确认(完整清单):
# 1. 版本与认证
claude --version # 正常输出版本号
# 会话内:/status 显示已登录
# 2. 全局配置
ls ~/.claude/CLAUDE.md # 全局指令存在
cat ~/.claude/settings.json # 权限/hooks/env 配置在
ls ~/.claude/keybindings.json # 键位绑定在
# 3. 扩展(skills/agents/commands/plugins)
ls ~/.claude/skills/ # 自定义 skill 齐全
ls ~/.claude/agents/ # 自定义 agent 齐全
ls ~/.claude/commands/ # 自定义 command 齐全
ls ~/.claude/plugins/ # 插件齐全
# 会话内:/plugin 能看到并启用原插件
# 4. 记忆
ls ~/.claude/projects/*/memory/MEMORY.md
# 会话内:/memory 内容出现在上下文
# 5. MCP
jq '.mcpServers | keys' ~/.claude.json
# 会话内:/mcp 显示 connected
# 6. 对话历史
ls ~/.claude/projects/*/*.jsonl | wc -l # 会话 jsonl 数量与备份一致
# 7. skills 实际可用
# 会话内调用一个自定义 skill 验证预期:全部通过。任何一项失败,定位到对应小节的失败边界排查。
31.9 失败边界与回滚
六大陷阱(迁移前必读)
| 陷阱 | 后果 | 对策 |
|---|---|---|
整体覆盖 .claude.json | 带入旧 userID/machineID,污染新设备统计,OAuth 错乱 | 只合并 mcpServers,用 jq |
| 迁移 OAuth token | token 失效 + 泄露风险 | 新机重新 /login |
| 用 cmux 等第三方封装 | 版本/凭据不可控 | 官方 native 安装 |
把 ~/claude/ 代码仓库当 CC 数据打包 | 备份膨胀到 GB 级 | 代码走 git/rsync 单独迁 |
带运行时目录(cache/ shell-snapshots/) | 含旧机绝对路径,污染新机 | 备份时排除 |
.claude.json 明文进公开仓库 | 身份字段 + 可能的 MCP 密钥泄露 | 当密钥文件,.gitignore 忽略 |
回滚
整个恢复过程非破坏性,回滚成本低:
.claude.json合并前已自动备份为~/.claude.json.bak.<时间戳>,回滚:bashcp ~/.claude.json.bak.* ~/.claude.json- 配置文件复制是"只新增不覆盖",如果新机原本有同名文件被覆盖了,从备份包里重新解压对比
- 对话导出脚本是增量覆盖,清空重跑:
rm -rf ~/claude/_backups/conversations-md && python3 export_conversations_md.py
一个容易被忽略的坑:记忆路径编码
~/.claude/projects/ 下的子目录名是当前工作目录的编码(比如 -Users-mba-claude 对应 /Users/mba/claude)。换机后如果用户名变了(比如从 mba 变成 mba2),新机的 cwd 编码变成 -Users-mba2-claude,而备份里是 -Users-mba-claude,记忆不会自动加载。
对策:换机后用户名保持一致;如果必须改用户名,把 ~/.claude/projects/ 下的目录名也跟着改:
# 假设旧用户名 mba,新用户名 mba2
mv ~/.claude/projects/-Users-mba-claude ~/.claude/projects/-Users-mba2-claude
# 同时检查 memory 文件内的绝对路径引用
grep -rl '/Users/mba/' ~/.claude/projects/-Users-mba2-claude/memory/ | \
xargs sed -i '' 's|/Users/mba/|/Users/mba2/|g'这个坑不在原始迁移清单里,但用户名变更场景一定会踩。
31.10 小结
Claude Code 的备份与迁移,浓缩成三句话:
- 分清三件资产——配置、上下文、对话,各有各的迁法
.claude.json只合并 mcpServers——整体覆盖是最大的坑,身份字段和 OAuth 永远保留新机原值- 凭据不迁、缓存不迁、代码单独迁——这三类东西混进备份包,要么失效要么污染
备份不是一次性动作,而是工程纪律。新增 MCP、改了 settings、写了新 skill 后,重跑备份命令。对话导出脚本定期跑一次增量。这样下次换机或重装,一套脚本半小时恢复全部状态。
最后强调一句:备份的价值不在"丢过多少次",而在"恢复时能不能跑通"。一份没在新机验证过的备份,等于没备份。本章的方案在 2026-06-30 真实换机时跑通,但你的环境不同——第一次迁移时,务必在旧机还在的时候,找一台备用机器完整演练一次。
上一章:第 30 章 一人公司系统性框架 | 下一章:第 32 章 多模型与成本策略