HomeArticle

The most high-profile AI rewrite in history: Claude took 11 days to complete Bun, while the founder spent a month before daring to reveal the truth

极客邦科技InfoQ2026-07-09 16:30
A month on, the summary from Bun's founder is finally here!

In May 2026, the Bun project completed a large-scale code migration that is nearly unprecedented in the history of software development.

This migration kicked off on May 3 and was officially merged into the main branch on May 14, taking only 11 days. The actual coding work lasted just 6 days, and the entire process was conducted publicly. However, Jarred Sumner spent nearly a month writing a blog to summarize the experience — far longer than the time spent writing code.

This JavaScript runtime originally consisted of 535,496 lines of Zig code (excluding comments). At the same time, around 20% of its code was written in C++, with multiple embedded C/C++ libraries. This time, with the help of AI, it was fully rewritten in Rust. The entire process involved over 1 million lines of code changes, 6,778 commits, and approximately 50 dynamic workflows running in Claude Code.

According to data disclosed by Sumner, this rewrite consumed 5.9 billion uncached input tokens, 690 million output tokens, and 72 billion cached input token reads at the API level, costing roughly $165,000 based on public API pricing.

Sumner stated that this represents the cutting edge of what current technology can achieve. He estimated that if three engineers fully familiar with the Bun codebase had completed this migration manually, it would have taken about a year — during which the team could barely make progress on new feature development, bug fixes, or security patches.

After this rewrite, Bun v1.3.14 became the final Zig-based release, while Bun v1.4.0 marks the first version built on Rust.

1  Outcome: From 6.7GB Memory Leak to Stable 609MB Usage

Bun started out as a Zig project with a remarkably broad scope: it functions as both a JavaScript and TypeScript transpiler, a bundler, a package manager, a test runner, a module resolver, an HTTP and WebSocket client, and even implements the full Node.js API layer. This extensive feature set has helped Bun's CLI exceed 22 million monthly downloads, earning support from projects and companies including Vercel, Railway, DigitalOcean, Claude Code, and OpenCode.

However, this very breadth also brought significant challenges to Bun.

Most notably in Bun v1.3.14, there was a long-standing headache for users: when executing consecutive Bun.build() calls, memory would accumulate continuously and never be released. Each build leaked roughly 3MB, which seems trivial at first — but if you run a development server that triggers a build on every request, memory would be slowly consumed until the process crashes.

In real-world tests, memory usage reached 1.9GB after 500 builds, 3.5GB after 1000 builds, 5.1GB after 1500 builds, and skyrocketed to 6.7GB after 2000 builds.

This was just the tip of the iceberg of Bun's memory issues. In the v1.3.14 bug fix checklist, Sumner documented a long list of problems:

When calling .reset() in the zlib module while an asynchronous .write() is still executing on the thread pool, the process crashes due to "use after free"; nested JavaScript callbacks in the http2 module trigger a hash table rehash, invalidating internal stream pointers; during iteration in UDPSocket.sendMany(), out-of-bounds writes occur if user code modifies the socket connection state via valueOf or toString callbacks; when the output buffer allocation fails for crypto.scrypt, the callback and protected password buffers are never freed; ...

All these bugs share a clear common root cause: mixing garbage collection (GC) with manual memory management in the same software.

Modern engines like JavaScriptCore (and V8) have extremely strict rules for exception handling and GC, while Zig — like C — does not handle memory automatically. When these two paradigms coexist in the same process, every memory allocation needs to be reviewed line by line: where are these bytes freed? How do we ensure they are freed only once? Are JavaScript exceptions properly checked? Are these GC-managed pointers visible to the conservative stack scanner? Is this memory managed by GC or manually?

What's more frustrating is that the team did not lack effort. They modified the Zig compiler to add Address Sanitizer (ASAN) support, ran ASAN tests in CI on every commit, used ReleaseSafe builds on Windows, ran 24/7 fuzz testing with Fuzzilli, and conducted extensive end-to-end memory leak tests. Even so, crash reports kept pouring in.

"Our bug list felt terrible. I got tired of going to sleep worrying that Bun would crash," Sumner wrote. He does not blame Zig — other Zig users never encountered such issues, because mixing GC and manual memory management is an extremely rare use case that almost no programming language is designed for.

The Rust version delivered a game-changing result: when running the same 2000 Bun.build() calls, memory usage remained stable at just 609MB.

Beyond fundamentally resolving the memory leak issues, the Rust rewrite brought multiple other improvements across different dimensions.

In terms of stability, v1.4.0 fixed 128 reproducible bugs from v1.3.14, ranging from memory leaks and crashes to incorrectly colored help text.

For binary size, combining the Rust rewrite, ICU adjustments, and identical code folding, Bun's binary file size on Linux and Windows was reduced by approximately 20%.

Performance saw a universal 2% to 5% improvement. Bun.serve went from 169,600 req/s to 177,700 req/s, while node:http performance increased from 103,800 to 108,500 req/s. In real-world scenarios, next build time dropped from 13.62 seconds to 13.03 seconds, and tsc batch compilation time decreased from 0.94 seconds to 0.89 seconds.

After Claude Code switched to the Rust-based Bun, its Linux startup time fell from 517ms to 464ms — roughly a 10% speedup.

2  Method: 64 Claude Instances, 11 Days, 50 Workflows

How Sumner pulled this off is the most notable part — because the method he used is very different from the traditional "let AI write code" approach.

Sumner split the entire process into roughly 50 dynamic workflows, each forming a continuous loop. He described this pattern using pseudocode in his blog:

Each task has a corresponding context (such as a Jira ticket or GitHub issue). Claude writes code based on this context, then two reviewers (also Claude instances) audit the code, and finally the feedback is applied before moving on to the next task.

This pattern ran through the entire rewrite process. Each workflow was responsible for a specific goal:

  • Generating a porting guide that maps Zig patterns and types to their Rust equivalents;
  • Mechanically porting every .zig file to a corresponding .rs file, aligned with PORTING.md and LIFETIMES.tsv;
  • Resolving compilation errors for each crate;
  • Getting subcommands like bun test or bun build up and running;
  • Passing every test in Bun's full test suite, followed by several rounds of large-scale refactoring and cleanup.

At peak times, Sumner ran 4 concurrent workflows, each containing 16 Claude instances — totaling 64 Claude agents working in parallel across 4 separate working trees, committing and pushing their own file changes. At the highest activity level, Claude wrote roughly 1,300 lines of code per minute.

This "implementer / reviewer" separation design was critical. The Claude writing code has a natural bias to get its code accepted, just like a human engineer. So the reviewers were completely separated from the implementers — reviewers only examined the code diff, had no access to the implementer's reasoning process, and were explicitly instructed to "assume the code is wrong". Each implementer was paired with two or more adversarial reviewers whose sole job was to find bugs.

Writing the code was only the first step. The original Zig code was a single compilation unit, but Rust required splitting it into roughly 100 crates to speed up compilation — and circular dependencies caused cargo check to output around 16,000 compilation errors all at once. This would be a disaster for a single human developer, but it was a manageable work queue for 64 parallel Claude instances. The workflow grouped errors by crate, running cargo check for each crate, assigning one Claude to fix the issues, two to review, and one to apply the changes.

Next, the team got bun --version running, then bun test. The test workflow randomly ran 100 test files at a time, sharded across 4 working trees. The test suite included several types of tests: some that ran for over a minute, some that exhausted the system's TCP connection limit, and some that forked nearly 10,000 processes. Sumner used systemd-run to create cgroups to limit resource usage, but the machine still crashed multiple times due to insufficient disk space.

After two days, the number of failing tests on Linux dropped from 972 to 23. A day and a half later, all Linux tests passed. Five days later, all six platforms — Linux x64, Linux arm64, macOS x64, macOS arm64, Windows x64, Windows arm64 — had a fully green test suite.

On May 14, PR #30412 was officially merged, with the entire test suite passing and no tests skipped or deleted.

3  Hidden Concerns: 13,000 unsafe Blocks and Code That Cannot Be Audited Line by Line

However, Sumner also acknowledged that this work is far from truly finished.

As of today, roughly 4% of Bun's Rust code resides in unsafe blocks, with about 13,000 unsafe keywords spread across ~27,000 lines of code, out of a total of ~780,000 lines of Rust code. 78% of these unsafe blocks are single-line, typically wrapping a pointer from C++ or a call to a C library.

He expects future refactoring to reduce this percentage. But some developers did the math: uv, another high-performance JavaScript tool, has ~350,000 lines of code with only 73 unsafe calls. Bun's unsafe count is 178 times that of uv — a gap that is hard to explain away by "needing to call C libraries".

Furthermore, undefined behavior has already been discovered even in safe Rust code. This is harder to debug than in C++, because you would naturally assume safe code cannot go wrong.

The Bun team later changed PathString::init in this problematic code to an unsafe fn.

Sumner himself admitted that this rewrite introduced 19 known regressions, noting that most of them came from code that looks syntactically identical but behaves semantically differently.

For example, these two code snippets look very similar but behave completely differently. Zig's assert is a function, so its arguments are executed in every build. Rust's debug_assert! is a macro, so in release builds the entire expression (including the insert_stale function call) gets completely removed.

Even though all discovered issues have been fixed, that does not mean there are no remaining problems in the million lines of AI-generated code.

Who in their right mind would immediately migrate their production application to a runtime that has been completely rewritten? It would be naive to assume that v1.4 introduces no new bugs or behavioral changes.

Another unavoidable issue is code review. A million lines of changes cannot actually be reviewed line by line by humans — even if you review one line per minute, it would take 11.7 straight days. At a realistic code review pace (200 lines per hour), it would take more than two years to finish auditing all the code.

The reviewers for this PR were primarily claude