Skip to content

第 31 章 备份与迁移

换 Mac、重装系统、给团队克隆一套环境——这些场景的共同痛点是:Claude Code 用了半年积累的配置、记忆、对话一旦丢失,重建成本远高于代码本身。本章给出一套已经落地验证的全量备份与迁移方案,核心是分清三类资产、避开 .claude.json 整体覆盖这个最大陷阱、凭据永远不迁移。素材来自 2026-06-30 的一次真实换机迁移,恢复脚本已 bash -n 校验并在新机跑通。

31.1 结论:备份本质是保护三件资产

Claude Code 的备份与迁移,本质是保护三件资产:

  1. 配置资产——~/.claude/ 目录(settings.jsonagents/skills/commands/output-styles/keybindings/plugins/)与 ~/.claude.json(MCP 服务器定义、设备身份字段)
  2. 上下文资产——全局 ~/.claude/CLAUDE.md、项目级 CLAUDE.md~/.claude/projects/ 下的 memory 文件与会话历史 jsonl
  3. 对话资产——~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl 的全部对话记录(主会话 + 子代理会话)

换 Mac 的默认方案是全量备份 + 分层恢复:把三件资产打成 tar.gz,在新机上分层复制配置、合并(而非覆盖).claude.json、凭据重新登录。这条路最直接、最安全、可回滚。

为什么不全靠云同步(iCloud/Dropbox)把 ~/.claude/ 整个同步过去?因为 Claude Code 的资产里混着三类不能无脑同步的东西:

  • OAuth 凭据——设备绑定,同步过去会失效,且可能泄露
  • 运行时缓存——cache/shell-snapshots/telemetry/sessions/ 含旧机绝对路径,同步过去会污染新机
  • .claude.json 身份字段——userIDmachineID 整体覆盖会污染新设备统计、导致 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.jsonmcpServers字段级合并只搬 mcpServers,不动身份字段
OAuth 凭据~/.claude.jsonoauthAccount不迁新机重新 /login
设备身份~/.claude.jsonuserID/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 份,旧的自动删除。

全量备份命令

bash
# 全量备份(滚动保留最近 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

预期输出:

text
claude-full-backup-20260708.tar.gz  约 40M(规模因记忆/会话量而异)
# tar tzf 输出前几行
.claude/CLAUDE.md
.claude/settings.json
.claude/skills/
.claude/skills/port-debug/SKILL.md
...

验证:

bash
# 备份大小与条目数
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) 条件追加

定期自动备份(可选)

bash
# 加到 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 为准):

bash
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否(只新增)
5jq 合并 mcpServers~/.claude.json,自动备份原文件否(有 .bak)
6验证版本/记忆加载/MCP 连通/skills 可用

整个流程非破坏性:只复制用户资产 + 合并配置,新机原有数据不动。第 5 步是唯一需要警惕的环节,下一节单独讲。

验证(恢复后逐项):

bash
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 密钥。整体覆盖会带来三重灾难:

  1. 身份污染——带入旧机的 userIDmachineID,新设备统计错乱,Anthropic 后台看到一台"幽灵设备"
  2. OAuth 错乱——旧机的 oauthAccount token 是设备绑定的,复制过去既失效又可能触发安全告警
  3. 配置丢失——新机如果在安装后已经生成了自己的 .claude.json(比如装了别的 MCP),整体覆盖会丢新机已有配置

正确做法:只合并 mcpServers 字段,其余字段保留新机原值。

jq 合并命令

bash
# 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

预期输出:

text
# jq '.mcpServers | keys' 输出
[
  "web-search-prime",
  "fetch",
  "other-mcp"
]

验证:

bash
# 启动 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 永远不迁移。原因:

  1. 设备绑定——Anthropic 的 OAuth token 与设备指纹关联,复制到新机直接失效
  2. 泄露风险——token 进备份包,备份包流转过程中可能泄露
  3. 安全审计——新机登录会在 Anthropic 后台生成新会话记录,便于审计;迁移旧 token 会绕过这个记录

正确做法:新机装完 native 后,首次运行 claude 会触发 OAuth 流程,浏览器跳转完成登录。这样新机拿到自己的 token,与设备绑定,干净。

bash
# 新机首次启动
claude
# 浏览器跳转 → 完成登录 → 回到终端看到欢迎信息

验证:

bash
# 会话内执行
/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

导出命令

bash
python3 /Users/mba/claude/_backups/export_conversations_md.py

预期输出:

text
# 扫描 ~/.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 文件名本身就是唯一的会话标识,用它做文件名不会撞。

python
# 错误(第一版,丢数据)
filename = f"{message.sessionId}.md"

# 正确(修复后)
filename = f"{date}_{Path(jsonl_path).stem}.md"

另一个坑:子代理 jsonl 不在项目顶层,而在 <项目>/<sid>/subagents/ 深层。必须用 glob('**/*.jsonl', recursive=True) 递归扫描,否则只导出主会话,漏掉全部子代理记录。

验证:

bash
# 主会话数量
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 迁移验证清单

恢复完成后,逐项确认(完整清单):

bash
# 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 tokentoken 失效 + 泄露风险新机重新 /login
用 cmux 等第三方封装版本/凭据不可控官方 native 安装
~/claude/ 代码仓库当 CC 数据打包备份膨胀到 GB 级代码走 git/rsync 单独迁
带运行时目录(cache/ shell-snapshots/)含旧机绝对路径,污染新机备份时排除
.claude.json 明文进公开仓库身份字段 + 可能的 MCP 密钥泄露当密钥文件,.gitignore 忽略

回滚

整个恢复过程非破坏性,回滚成本低:

  • .claude.json 合并前已自动备份为 ~/.claude.json.bak.<时间戳>,回滚:
    bash
    cp ~/.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/ 下的目录名也跟着改:

bash
# 假设旧用户名 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 的备份与迁移,浓缩成三句话:

  1. 分清三件资产——配置、上下文、对话,各有各的迁法
  2. .claude.json 只合并 mcpServers——整体覆盖是最大的坑,身份字段和 OAuth 永远保留新机原值
  3. 凭据不迁、缓存不迁、代码单独迁——这三类东西混进备份包,要么失效要么污染

备份不是一次性动作,而是工程纪律。新增 MCP、改了 settings、写了新 skill 后,重跑备份命令。对话导出脚本定期跑一次增量。这样下次换机或重装,一套脚本半小时恢复全部状态。

最后强调一句:备份的价值不在"丢过多少次",而在"恢复时能不能跑通"。一份没在新机验证过的备份,等于没备份。本章的方案在 2026-06-30 真实换机时跑通,但你的环境不同——第一次迁移时,务必在旧机还在的时候,找一台备用机器完整演练一次。


上一章:第 30 章 一人公司系统性框架 | 下一章:第 32 章 多模型与成本策略