史上最高调的AI重写:Claude花11天搞定Bun,创始人花一个月才敢交底
<p data-type="paragraph">2026 年 5 月,Bun 项目完成了一次在软件开发史上近乎罕见的大规模代码迁移。</p><p data-type="paragraph"></p><p data-type="paragraph">这次迁移从 5 月 3 日启动,到 5 月 14 日正式合并入主分支,只用了 11 天。写代码只用了 6 天,并且整个过程公开。但 Jarred Sumner 写博客总结却花了快一个月,比写代码的时间长多了。</p><p data-type="paragraph"></p><p data-type="paragraph">这个 JavaScript 运行时原本拥有 535,496 行 Zig 代码,不包括注释;同时,约 20% 的代码由 C++ 编写,并嵌入了多个 C/C++ 库。此次借助 AI 重写为 Rust,整个过程涉及超过 100 万行代码变更、6778 次提交,并在 Claude Code 中运行了大约 50 个动态工作流(dynamic workflows)。</p><p data-type="paragraph"></p><p data-type="paragraph">根据 Sumner 披露的数据,这次重写在 API 层面消耗了 59 亿个未缓存输入 token、6.9 亿个输出 token,以及 720 亿个缓存输入 token 读取,按 API 定价计算约花费 16.5 万美元。</p><p data-type="paragraph"></p><p data-type="paragraph">Sumner 说,这是目前技术所能达到的前沿水平。他估计,如果让 3 名完全熟悉 Bun 代码库的工程师手工完成这次迁移,大约需要一年时间,而且在这一年里,团队几乎无法继续推进新功能开发、bug 修复和安全修复。</p><p data-type="paragraph"></p><p data-type="paragraph">这次重写之后,Bun v1.3.14 成为最后一个 Zig 版本,Bun v1.4.0 将成为第一个 Rust 版本。</p><p data-type="paragraph"></p><h2 data-type="heading" data-id="heading0">成果:从 6.7GB 内存泄漏到 609MB 稳定</h2><p data-type="paragraph"></p><p data-type="paragraph">Bun 最初是一个 Zig 项目,覆盖面非常广:它既是 JavaScript 和 TypeScript 转译器,也是打包器、包管理器、测试运行器、模块解析器、HTTP 和 WebSocket 客户端,还实现了 Node.js API 层。正是这样的产品宽度,让 Bun 的 CLI 月下载量超过 2200 万次,并获得了 Vercel、Railway、DigitalOcean、Claude Code 和 OpenCode 等项目或公司的支持。</p><p data-type="paragraph"></p><p data-type="paragraph">但同样是这种宽度,也给 Bun 带来了一些挑战。</p><p data-type="paragraph"></p><p data-type="paragraph">特别是在 Bun v1.3.14 中,有一个让大家头疼已久的问题:连续执行 Bun.build()调用时,内存会不断累积,永不释放。每次构建大约泄漏 3MB,看起来不多,但如果你运行的是一个开发服务器,每次请求都触发一次构建,那么内存就会被一点点吞噬,直到进程崩溃。</p><p data-type="paragraph"></p><p data-type="paragraph">在实际测试里,执行 500 次构建后内存占用 1.9GB,1000 次后 3.5GB,1500 次后 5.1GB,2000 次后飙升到 6.7GB。</p><p data-type="paragraph"></p><p data-type="paragraph"></p><p data-type="paragraph">这只是诸多内存问题的冰山一角。在 v1.3.14 的 bug 修复清单中,Sumner 列出了一长串问题:</p><p data-type="paragraph"></p><p data-type="paragraph">zlib 模块里调用.reset()时,如果还有一个异步.write()正在线程池中执行,进程就会因为"堆释放后使用"而崩溃;http2 模块里嵌套的 JavaScript 回调触发了哈希表重哈希,导致内部流指针失效;UDPSocket.sendMany()在遍历过程中,如果用户代码通过 valueOf 或 toString 回调改变了套接字的连接状态,就会发生越界写入;crypto.scrypt 在输出缓冲区分配失败时,回调和受保护的密码缓冲区永远得不到释放;......</p><p data-type="paragraph"></p><p data-type="paragraph">这些 bug 的共性非常明显——它们几乎都指向同一个根源:将 GC 与手动内存管理在同一个软件里混合使用。</p><p data-type="paragraph"></p><p data-type="paragraph">JavaScriptCore(以及 V8)这样的现代引擎对异常处理和 GC 有极其严格的规则,而 Zig 像 C 语言一样不会自动管理内存。当这两种范式在同一个进程中,每个内存分配都需要被逐行审查:这些字节在哪里被释放?怎么确保只释放一次?是否正确检查了 JavaScript 异常?这个被 GC 管理的指针对保守栈扫描器可见吗?这是 GC 内存还是手动管理的内存?</p><p data-type="paragraph"></p><p data-type="paragraph">更令人焦虑的是,团队并非没有努力。他们已经给 Zig 编译器进行了修改,加了 Address Sanitizer 支持(ASAN),每次提交都在 CI 中运行 ASAN 测试,在 Windows 上使用 ReleaseSafe 构建,用 Fuzzilli 做 24/7 的模糊测试,还有大量端到端的内存泄漏测试。即便如此,崩溃报告仍然源源不断。</p><p data-type="paragraph"></p><p data-type="paragraph">“我们的 bug 修复列表让人感觉很糟糕,我厌倦了带着对 Bun 崩溃的担忧去睡觉。”Sumner 写道。他并不责怪 Zig——其他 Zig 用户并没有遇到 Bun 这样的问题,因为将 GC 与手动管理内存混合使用,本身就是一种极罕见的需求,几乎没有语言为此设计。</p><p data-type="paragraph"></p><p data-type="paragraph">而 Rust 版本交出的答卷是:同样执行 2000 次 Bun.build(),内存稳定在 609MB。</p><p data-type="paragraph"></p><p data-type="paragraph">除了内存泄漏问题得到根本性解决,Rust 重写还带来了其他几个维度的改善。</p><p data-type="paragraph"></p><p data-type="paragraph">在稳定性方面,v1.4.0 修复了 v1.3.14 中可复现的 128 个 bug,从内存泄漏到崩溃到颜色显示错误的帮助文本都得到了解决。</p><p data-type="paragraph"></p><p data-type="paragraph">从体积上,结合 Rust 重写、ICU 更改和相同的代码折叠,Bun 在 Linux 和 Windows 上的二进制文件大小减少了约 20% 。</p><p data-type="paragraph"></p><p data-type="paragraph"></p><p data-type="paragraph">在性能方面,普遍提升了 2%到 5%。Bun.serve 从 16.96 万 req/s 提升到 17.77 万 req/s,node:http 从 10.38 万提升到 10.85 万。实际应用场景中,next build 从 13.62 秒降至 13.03 秒,tsc 批量编译从 0.94 秒降至 0.89 秒。</p><p data-type="paragraph"></p><p data-type="paragraph">而 Claude Code 在基于 Rust Bun 发布后,Linux 上的启动时间从 517ms 降至 464ms,快了约 10%。</p><p data-type="paragraph"></p><p data-type="paragraph"></p><h2 data-type="heading" data-id="heading1">方法:64 个 Claude,11 天,50 个工作流</h2><p data-type="paragraph"></p><p data-type="paragraph">Sumner 是怎么做到的,这可能是最值得关注的部分——因为他用的方法,和传统的“让 AI 写代码”不同。</p><p data-type="paragraph"></p><p data-type="paragraph">Sumner 把整个过程拆成了大约 50 个动态工作流,每个工作流都是一个循环。他在博客里用伪代码描述了这个模式:</p><p data-type="paragraph"></p><p data-type="paragraph"></p><p data-type="paragraph">每个任务都有一个上下文(比如一个 Jira ticket 或 GitHub issue),Claude 基于这个上下文写出代码,然后两个审查者(也是 Claude)审查代码,最后应用反馈。完成之后,再取下一条任务。</p><p data-type="paragraph"></p><p data-type="paragraph">这种模式贯穿了整个重写过程。每个工作流负责一个特定目标:</p><ul data-type="bulletedlist"><li data-type="listitem"><p data-type="paragraph">生成一份 porting guide,把 Zig 的模式和类型映射到 Rust 的模式和类型;</p></li><li data-type="listitem"><p data-type="paragraph">把每个 .zig 文件机械式移植成一个 .rs 文件,并匹配 <a href="https://github.com/oven-sh/bun/commit/46d3bc29f270fa881dd5730ef1549e88407701a5" title data-type="link">PORTING.md</a> 和 LIFETIMES.tsv;</p></li><li data-type="listitem"><p data-type="paragraph">修复每个 crate 的编译错误;</p></li><li data-type="listitem"><p data-type="paragraph">让 bun test 或 bun build 这样的 subcommand 跑起来;</p></li><li data-type="listitem"><p data-type="paragraph">让 Bun 整个测试套件里的每个测试都通过;做几轮大型重构和清理。</p></li></ul><p data-type="paragraph"></p><p data-type="paragraph">峰值时期,Sumner 同时运行了 4 个工作流,每个工作流里 16 个 Claude,总共 64 个 Claude 同时在 4 个工作树中并行工作,各自提交和推送文件。在最高峰时,Claude 每分钟写了约 1300 行代码。</p><p data-type="paragraph"></p><p data-type="paragraph">这种“实现者/审查者”的分离设计是关键。写代码的 Claude 想让代码被接受,这和人类工程师一样有偏见。所以审查者和实现者完全分开——审查者只看代码差异,不看实现者的推理过程,而且被明确告知“假设代码是错的”。每个实现者对应两个以上的对抗性审查者,审查者的唯一工作就是找 bug。</p><p data-type="paragraph"></p><p data-type="paragraph"></p><p data-type="paragraph">代码写完只是第一步。Zig 代码是单一编译单元,而 Rust 要拆分成约 100 个 crate 来加快编译速度,循环依赖导致了 cargo check 一次性输出约 16000 个编译错误。对于一个人来说这是灾难,但对于 64 个并行工作的 Claude 来说,这是可以处理的工作队列。工作流把错误按 crate 分组,每个 crate 跑一遍 cargo check,一个 Claude 修复,两个审查,一个应用修改。</p><p data-type="paragraph"></p><p data-type="paragraph">接下来是让 bun --version 跑起来,然后是 bun test。测试工作流每次随机跑 100 个测试文件,分片到 4 个工作树。测试套件也包含好几种:有些测试会运行超过一分钟,有些会耗尽系统 TCP 连接数,有些会 fork 约 10000 个进程。Sumner 用 systemd-run 创建 cgroup 来限制资源,但机器还是因为磁盘空间不足崩溃了好几次。</p><p data-type="paragraph"></p><p data-type="paragraph">两天后,Linux 平台的失败测试从 972 个降到了 23 个。一天半之后,Linux 全绿了。五天后,全部六个平台——Linux x64、Linux arm64、macOS x64、macOS arm64、Windows x64、Windows arm64——全部通过。</p><p data-type="paragraph"></p><p data-type="paragraph">5 月 14 日,PR #30412 正式合并,测试套件全部通过,没有跳过或删除任何测试。</p><p data-type="paragraph"></p><p data-type="paragraph"></p><h2 data-type="heading" data-id="heading2">隐忧:13,000 个 unsafe 和无法逐行审查的代码</h2><p data-type="paragraph"></p><p data-type="paragraph">不过,Sumner 也承认,这项工作还没有真正结束。</p><p data-type="paragraph"></p><p data-type="paragraph">截至目前,Bun 的 Rust 代码中大约有 4% 位于 unsafe block 内,约 13,000 个 unsafe 关键字,分布在约 27,000 行代码中,而 Rust 总代码量约为 780,000 行。其中 78% 的 unsafe block 只有一行,通常是一个来自 C++ 的指针,或者一次对 C 库的调用。</p><p data-type="paragraph"></p><p data-type="paragraph">他预计后续重构会让这个比例降下来。但有人算了一笔账:uv 约 35 万行代码,只有 73 次 unsafe 调用。而 Bun 的 unsafe 数量是 uv 的 178 倍。这个差距很难用“需要调用 C 库”来解释。</p><p data-type="paragraph"></p><p data-type="paragraph">并且随后还在安全 Rust 代码中也暴露了未定义行为。这比 C++还难调试,因为你会以为安全代码不可能出问题。</p><p data-type="paragraph"></p><p data-type="paragraph"></p><p data-type="paragraph">Bun 团队随后把这个问题中的 PathString::init 改成了 unsafe fn。</p><p data-type="paragraph"></p><p data-type="paragraph">Sumner 自己也承认,这次重写引入了 19 个已知的回归问题,并表示大多数回归问题都源于语法相同但语义不同的代码。</p><p data-type="paragraph"></p><p data-type="paragraph"></p><p data-type="paragraph">比如这两个代码片段看起来很相似,但行为却截然不同。Zig 的代码 assert 是一个函数,因此它的参数在每次构建中都会运行。Rust 的代码 debug_assert!是一个宏,因此在发布版本中,整个表达式(包括函数调用)都会被删除 insert_stale。</p><p data-type="paragraph"></p><p data-type="paragraph">虽然已经问题都修了,但这并不表示百万行 AI 代码就没有其他问题。</p><p data-type="paragraph"></p><p data-type="paragraph"></p><p data-type="paragraph">正常人谁会在一个运行时被彻底重写之后,立刻把自己的生产应用迁过去?</p><p data-type="paragraph">如果以为 1.4 版本没有引入新的 bug,或者没有带来行为变化,那就太天真了。</p><p data-type="paragraph"></p><p data-type="paragraph">还有一个不能忽略的事情是代码审查。100 万行的变更实际上没法由人类逐行看——就算一秒钟看一行,也要连续看 11.6 天;按实际的代码审查速度(一小时 200 行),要两年多才能看完。</p><p data-type="paragraph"></p><p data-type="paragraph">这次 PR 的审查者主要是 claude[bot]和 coderabbitai[bot]。Sumner 自己也承认,他的审查方式是“检查对抗性审查 agent 是否正确捕获了差异,确保转换指南被遵守,同时自己也手动读了不少代码”。但“不少”是多少,他没说。</p><p data-type="paragraph"></p><p data-type="paragraph">还有一个绕不开的问题:Bun 在 2025 年 12 月被 Anthropic 收购了,真正能有效维护这套代码库的工具,基本只有 Claude 自己。社区里有人说,这已经算不上传统意义上的开源项目了——你想给 Bun 提 PR,得先订阅 Anthropic,或者指望那几个已经看懂了 AI 生成代码的核心成员。</p><p data-type="paragraph"></p><h3 data-type="heading" data-id="heading3">16.5 万美元换一年工作量,值吗?</h3><p data-type="paragraph"></p><p data-type="paragraph">Sumner 在博客中还披露,这次重写的 API 成本约为 16.5 万美元,等于 3 名工程师一年的工作量。这个数字在 Hacker News 上引发了激烈的讨论。</p><p data-type="paragraph"></p><p data-type="paragraph">有人认为,这笔账其实很划算。16.5 万美元在硅谷请不了几个全职工程师,更不用说 Anthropic 这种级别公司的工程师了。按照 levels.fyi 上的薪资数据,Anthropic 工程师的总包很可能达到 50 万美元甚至更高。即便按 50 名工程师平均年薪 33.6 万美元粗略计算,折合到每天大约是 1292 美元。50 个人连续工作 11 天,光人力成本就已经接近 71 万美元,还不包括福利、办公场地、设备和其他管理开销。</p><p data-type="paragraph"></p><p data-type="paragraph"></p><p data-type="paragraph">但是,Sumner 用的是“Claude Fable 5 的预发布版本”,一个尚未对公众开放、可能受出口管制的高阶模型。所以 API 定价只是最终用户看到的数字,背后是 Anthropic 投入的巨额研发费用。还有人指出,把成本简化成 API 定价,是在刻意淡化真实投入。如果算上模型研发成本、训练成本、算力投入、工程人力等,相信最终的总成本肯定很高,很可能超过 150 万美元。</p><p data-type="paragraph"></p><p data-type="paragraph"></p><p data-type="paragraph">而且目前看来,虽然 16.5 万美元换一年工作量,账面上看挺划算。</p><p data-type="paragraph"></p><p data-type="paragraph">但真正的成本不在这张账单上。这个代码库有 6778 次提交,没有一个人从头到尾完整读过,虽然眼下一切正常,可六个月后呢?当某个诡异的并发问题在凌晨三点突然冒出来,负责值班的工程师面对的是一个连他自己都说不清内部逻辑的系统。延伸到以后都得 AI 来维护,维护成本怎么算,其实挺难。</p><p data-type="paragraph"></p><p data-type="paragraph">参考链接:</p><p data-type="paragraph"><a href="https://bun.com/blog/bun-in-rust" title data-type="link">https://bun.com/blog/bun-in-rust</a></p><p data-type="paragraph"><a href="https://hn.edgecompute.app/item/48837877" title data-type="link">https://hn.edgecompute.app/item/48837877</a></p>