Anthropic 如何用 Claude Code 执行大规模代码迁移
发布日期: 2026年7月16日
分类: Claude Code
来源: https://claude.com/blog/ai-code-migration
直到最近,代码迁移,也就是把生产代码库迁至新语言的项目,通常需要多年时间。
上个月,Anthropic 的开发者使用 Claude Fable 5、Claude Opus 4.8 和动态工作流,迁移了 10 个规模从数万到数十万行代码不等的软件包。本文介绍其中两个案例和由此形成的最佳实践。
Bun 联合创始人、Anthropic 技术人员 Jarred Sumner 用 Claude Code 将 Bun 从 Zig 迁到 Rust。不到两周,迁移生成 100 万行代码;Bun 原有测试套件在合并前 100% 通过 CI,合并后出现 19 个回归问题,均已修复,Rust 版本于 6 月发布。
Anthropic Labs 联合负责人 Mike Krieger 则在一个周末把 Python 代码库迁移成 165,000 行 TypeScript:包括数百个 Agent、八道 stage gate、三轮 adversarial review,最后将每个命令的输出同 Python 原版逐一比较以确认 parity。
Claude Code 的新能力改变了此类长期被搁置项目的成本结构。下面这套六步流程来自这些迁移的经验。
核心洞见是:不应逐个修代码,而应修复生成代码的过程,也就是闭环本身。
什么是 AI 代码迁移?
AI 代码迁移是利用 AI Agent 将生产代码库迁移至新语言或框架。工程师不再手动逐文件翻译,而是制定迁移规则和验证闭环;Agent 负责翻译、编译和测试,直至新代码行为与原始代码一致,从而把原先需要多年的项目压缩为数周。
为什么、又该何时迁移语言?
在讨论如何迁移前,先讨论何时、为什么迁移,因为围绕这类项目的假设已经改变。
团队启动迁移,通常是因为最初构建时的条件已不复存在:已知权衡逐渐受限、出现了更佳方案,或原生态正在萎缩。
例如 Jarred 最初选择 Zig,是因为它具备 C 级性能且极为简单,适合独立创始人在奥克兰狭小公寓中花一年做出 Bun。这种简单性也有已知取舍,他在文章中写过。到 2026 年,Bun CLI 月下载量已超过 1,000 万,且被 Claude Code 广泛使用。
就在上一季度,这些取舍仍不足以证明冻结路线图、投入数季度资源的合理性。换语言可能带来更小、更快、更安全的系统,但没有人愿意为之付费。工程师还要承担职业风险:若同时维护两个代码库数季甚至数年,最后只达到 90% parity,麻烦反而比开始时更大。
如今最坏情况只是删除分支、重新尝试。迁移仍需合理业务理由,而且不免费:百万行迁移虽不再需要四年、三四百万美元工程投入,执行仍可能耗费数万或数十万美元。Bun 迁移消耗 59 亿未缓存 input token 和 6.9 亿 output token,按 API 定价约 165,000 美元;Mike 的迁移主体使用 2,700 万 token。

Jarred 的百万行 PR。
但所需的迁移理由已不必像过去那样重大。变更记录中一年积累的内存错误修复,或一个长期性能瓶颈,可能就已足够。Mike 的项目由编译环节推动:其团队内部工具以单一 binary 交付。Python toolchain 为各平台生成 binary 约需 8 分钟,一个 release 的 build matrix 共需等待 30 分钟;迁移后同样编译约 2 秒完成,binary 启动快 6 倍,团队还能取消独立部署 pipeline。
为什么 AI 改变了代码迁移的成本结构
Claude Fable 5 是 Anthropic 能力最强、面向公众提供的模型。Fable 和 Opus 4.8 特别擅长委派、指导、验证 subagent 的并行工作流,并寻找达成既定目标的多种路径。
大规模代码迁移尤其适合这类高级模型,原因包括:
- 工作可并行。 数千个独立单元,例如文件或 crate,可同时处理,而非相互等待。
- 上下文清晰且完整。 旧代码本身就是很好的 specification,也可作为迁移 Agent 遵循指南的核心参考。
- 内置裁判存在。 大型代码库往往有测试套件,Agent 可用来验证工作。验证客观时,模型表现最好,因为它可在无需人工裁判的情况下长期迭代并接近事实。
- 队列会自行生成。 compiler 或 test 失败会成为 Agent 下一个要修的问题。
- 需要一致性与边缘情况处理。 流程设计会让偏差无处隐藏:reviewer 会引用每项发现背后的规则,因此违规会进入队列,而非变成悄无声息的分歧;Agent 处理过一个 edge case 后,修复会成为后续 Agent 共同遵循的规则。
如后文所示,Mike 和 Jarred 都在迁移关键步骤使用 Fable,尤其通过使用不同 model class 的 advisor strategy 来优化 token 支出。
大规模代码迁移的六个步骤
下列流程已被泛化为适用于不同语言和场景。更多细节见 Jarred 的博客和迁移 starter kit。starter kit 是本文过程的通用模板,并非这两个具体项目所使用的配置。
前提条件
迁移开始前,必须拥有一个足够可靠的“裁判”,否则既没有退出条件,也无法衡量成功。这个裁判必须能平等评估源代码与目标代码。源语言测试套件往往依赖目标代码中并不存在的内部函数。
构建裁判的方法如下:
- 分类已有测试。 用 Claude 判断哪些测试可表示为外部调用,哪些依赖不可移植的内部结构。
- 改写为可移植测试。 将面向外部的测试改成可同时运行于源代码和迁移版本的断言;再用 adversarial Agent 验证改写没有削弱断言。
- 验证裁判。 先对源代码运行以确认通过,再对故意破坏的代码运行以确认失败。检测不出破坏的裁判不是真裁判。
Jarred 恰好拥有由第三种语言 TypeScript 写成的大型测试套件,但多数项目没有。Python 到 TypeScript 的迁移中,Mike 为七个真实场景构建 parity harness,任何行为差异均视为需修复的 bug。
下图有助于理解后续各阶段。它主要遵循 Jarred 的路径,每一阶段都包含审查与 gate。Mike 用类似闭环和大致相同结构,但会端到端跑完整迁移,根据输出调整规则和工作流后再跑,每次丢弃结果,直到第三次运行。

第一步:建立 rulebook、dependency map 和 gap inventory

此阶段建立迁移基础:列出需要重构而非单纯翻译的代码位置,编写如何翻译的 rulebook,并生成对迁移实现工作流排序用的 dependency map。
顺序重要:rulebook 必须先于 gap inventory。gap inventory 的定义就是 rulebook 默认规则覆盖不到的内容;二者会在联合审查中一同测试。
Rulebook
Rulebook的具体形态取决于启动时的关键架构决策,尤其是新代码保持原有结构,还是完全重新设计。
如果是前者,例如 Jarred 的项目,rulebook 主要是不同语言间类型与 idiom 的 lookup table,并指向难以翻译组件的 gap inventory;如果是后者,例如 Mike 的项目,它更像一份 design document。
Jarred 通过与 Claude 对话建立 rulebook,为每个模糊区域形成 policy;还使用 8 个专门设计的 subagent,分别按其经验检查 8 类常见失效模式。
Dependency map
需要理解文件依赖,才能有效拆分并行迁移工作流,确定先迁哪些文件、哪些必须放在同一 batch。某些语言和 codebase 具有显式 manifest,可使此事简单;但 legacy codebase 和 C/C++、Python 等常见语言,依赖需要被发现并画成图。
Claude Code 可调度 Agent 创建并运行确定性脚本生成该图。迁移 kit 中的 prompt使用 workflow 来建立 review-and-fix loop。请注意,starter kit 仍只是通用模板。
Gap inventory 与 skeptic reviewer
新语言和旧语言有不同的必须满足要求。Zig 到 Rust 的关键差异是手动内存管理,C 和 C++ 也有类似特征:
Zig:
fn readConfig(allocator: std.mem.Allocator) ![]u8 {
const buf = try allocator.alloc(u8, 1024);
// ...fill buf...
return buf; // caller must free this — but only the comment says so
}
// A caller that forgets 'defer allocator.free(buf)' still compiles — the leak only surfaces at runtime.Rust:
fn read_config() -> Vec<u8> {
let buf = vec![0u8; 1024];
// ...fill buf...
buf // ownership moves to the caller; memory is freed automatically
}
// Use it after it's moved? Free it twice? Neither compiles.
// Forget to free it? There's no free call to forget — drop is automatic.Python 到 TypeScript 的差异则在 interfaces 和 contracts:Python 不需要声明会接收何种对象、返回何种结果的 contract,TypeScript 需要:
Python:
def register(handler):
handler.setup()
return handler.run({"retries": 3})
# Any object with .setup() and .run() works here. Which objects actually get passed in? Read the whole codebase to find out.TypeScript:
interface RunResult { ok: boolean }
interface Handler
{ setup(): void;
run(opts: { retries: number }): Promise<RunResult>;
}
function register(handler: Handler): Promise<RunResult> {
handler.setup();
return handler.run({ retries: 3 }); }
// The contract must be written down before this compilesJarred 与 Mike 都以 gap inventory 文件记录这类隐性知识。Jarred 在开始前盘点 gap;Mike 则先翻译、后审计得到 gap inventory。实际项目可能两种都需要。可参考创建 gap inventory 的 Claude Code prompt。
第二步:对规则做压力测试

这一步是一次小型迁移,相当于为大迁移进行 shakedown cruise。Jarred 用一个 Agent 按 rulebook 翻译三个文件,一个 Agent “像资深 Rust 工程师一样”翻译三个文件,另一个 Agent 根据 diff 创建新翻译规则。此时他就发现了两个关键问题;若把它们扩散到全部 1,448 个文件,将造成大量问题。相应 prompt 可参考此处。
这种压力测试只适用于保留结构的迁移,即同一文件的两个译本可逐行比较。若 rulebook 是重设计,例如 Mike 的项目,等价做法是让 adversarial reviewer 直接攻击 design document,再用一次性端到端运行验证。无论如何,测试阶段翻译出的文件都应丢弃,目标是完善规则,而不是取得渐进式成果。
第三步:翻译全部内容

其余阶段采用相同 multi-agent loop:实施、审查、修复。可将 implementer 的工作分配给更小模型,把 reviewer 留给大模型;例如 Mike 在主迁移中并行调度 12 个 subagent 时使用 Claude Sonnet。
工作队列应是机械、可恢复的:batch script 通过检查磁盘上是否存在已翻译文件决定该做什么,再把待处理文件拆成 implementer Agent batch。因为队列每次都从磁盘重建,迁移天然可恢复。
这个阶段 Agent 可能对工作量过于谨慎。可用直截了当、强调的 prompt 纠正,并提醒它 compiler 会在下一步捕获错误。翻译器无法确信完成的内容应标记为 // TODO(port): <reason>,在第四步处理。
从这里开始,待办队列会自己增长:compiler 列出错误,smoke test 发现 crash,suite 报告失败。两名 adversarial reviewer 用不同 context 评估 implementer 的输出;reviewer 分歧提交给第三个 Agent。当 reviewer 在大量文件中反复发现同一种错时,不要逐文件修补,而是向 rulebook 加一句规则,并重新生成受影响 batch。至此 rulebook 不断增厚,代码不再被手工逐处打补丁。
还需决定 compiler 放在哪一环。Mike 在每个 loop 运行 TypeScript compiler,因为它可在数秒检查一个 unit;Jarred 则禁止 compiler 进入此环,推迟至下一步,因为 Cargo 需要数分钟。至此最重的工作已完成,prompt 也会变短。
第四、五、六步:编译、运行和匹配行为

这三步共享同一循环架构,且所需人类判断逐步减少,故合并说明。
第四步有时会按语言和规模并入第三步。若 compiler 很慢,Agent 甚至不直接运行它。Jarred 使用 orchestrator script 在整个 workspace 调一次 compiler;随后 fixer Agent 一边做 adversarial review,一边浏览错误列表;再重跑 build,如此循环。
错误列表有助于发现需调整的系统问题。Jarred 曾遇到数千个 Rust module error,这些错误来自修复 Zig lazy compilation 原先容忍的循环 import;他用编码逻辑对依赖分类,判断哪些边界应删除、移动或重组。
第五步也有类似 compiler error list 的机械事实源:smoke test crash。应把问题按 root cause 分组,再交 adversarial subagent 审查修复。
第六步是比较两个 codebase 的程序行为。文件已翻译、编译并通过 smoke test 后,应分片运行前提阶段建立的测试套件。让 fixer Agent 解决失败,它需对照两个代码库检查失败 test;adversarial reviewer 再检查修复。
此闭环的下一层是 build daemon,这是唯一允许 rebuild binary 的进程。fixer 写 patch,daemon 批量处理、只 rebuild 一次、重跑受影响测试并反馈结果。这样把最昂贵操作串行化,避免多个 Agent 各自触发。
同一失败在许多测试中重复时,修复应上移:修改产生错误的规则,只重新生成该规则触及的文件。
Mike 的做法尤其适合没有内置或可移植测试套件的项目。他让 Claude 写小脚本,对新迁移版本和原 Python codebase 运行 7 个真实场景并比较输出。每个失败场景都有对应 fixer Agent,闭环运行至 7 个场景全部通过。随后 Claude 自己设计端到端测试 suite,夜间自主运行,连续四晚修复失败后重跑,发现了任何场景列表都难以预见的细碎问题。
教训是:没有现成测试套件并不会阻止这一步。不能继承裁判,就让 Claude 建一个;无论如何,源代码库都是事实来源。
代码迁移最佳实践
每次运行都会带来此前没学到的经验,下一次迁移必然也会有本指南无法预见的内容。但以下做法适用于每个项目:
- 不要机械照搬本指南。 每次迁移都不同;应以此为起点,在作出承诺前与 Claude 规划具体迁移。
- 不要盯住单个失败。 单个失败是闭环的工作,fixer 会消化它;人应关注模式。
- 让审查具备对抗性,让验证机械化。 Adversarial review 能支撑更长任务,也往往值得 token 成本;让 script、compiler、diff 和 test suite 成为裁判。
- 不要把最大模型用于一切。 Token 消耗集中在闭环内,应谨慎设计。小模型适合大吞吐 implementer fan-out;大模型应留给 reviewer,以及会编写供其他 Agent 遵循规则的工作。
- 前置投入人类时间。 Rulebook 与压力测试最耗时,之后大部分是消化队列。
- 让工作队列机械且可恢复。 “完成”应被定义为“输出文件存在于磁盘上”。
审查闭环结果,而不是逐行审查代码
Jarred 的 Bun 迁移已投入生产,当然每次迁移都会有取舍。例如约 4% Rust 代码位于 unsafe block 中,大部分是 C/C++ 边界处的单行指针操作。
但新 codebase 明显更好。团队工具能检测到的所有 memory leak 均已修复:2,000 次重复 build 的 benchmark 内存从 6,745 MB 降至 609 MB;Linux 和 Windows 的 binary 小 19%;跨语言优化令 HTTP 服务和 next build、tsc 等实际工作负载提速 2 到 5 倍。
或许该重新计算那些长期被搁置迁移的收益了。选择一个你一直在容忍的 codebase,询问 Claude:迁移它的过程会是什么样。
相关资源
- 迁移 starter kit,它是上述过程的通用模板,并非本文具体项目所用配置;
- 代码现代化插件,用于 legacy modernization 与框架升级,而不是语言迁移;
- Claude Code 中的动态工作流。