Jarred Sumner used Claude Fable 5 and 64 orchestrated AI agents to rewrite Bun from Zig to Rust — 6,502 commits, 128 bugs fixed, and a 20% smaller binary. Here's how it happened.
On May 14, 2026, Jarred Sumner merged a pull request that fundamentally changed the JavaScript runtime landscape: Bun was rewritten from Zig to Rust. The port, completed in 11 days, used a pre-release version of Claude Fable 5 (Anthropic's Mythos-class model) running 64 AI agents in parallel across 4 worktrees.
The result: 6,502 commits, 0 tests skipped or deleted, 128 bugs fixed, a 20% smaller binary, 2-5% faster performance, and dramatically reduced memory usage. Bun v1.3.14 was the last Zig-based version; v1.4.0 is the first Rust-based release, available in canary now.
This is not just a story about a runtime rewrite — it's a case study in how AI-assisted software engineering at scale can accomplish what would have taken a team of engineers a full year, in 11 days, by one person with the right tools.
Bun began as a line-for-line port of esbuild's JavaScript & TypeScript transpiler from Go to
Zig. Jarred Sumner wrote his first line of Zig on April 16, 2021, after seeing Zig's
single-page language reference and getting excited about its low-level control and
performance characteristics. Without Zig, Bun would never have been built — the language's
simplicity and compile-time execution (comptime) enabled a single developer
to create a massive runtime in one year, pre-LLM, in a cramped Oakland apartment.
Bun's scope was enormous from the start: JavaScript, TypeScript, and CSS transpiler, minifier,
and bundler; npm-compatible package manager; Jest-like test runner; Node.js-compatible
module resolution; HTTP/1.1 & WebSocket client; and dozens of Node.js API implementations
like fs, net, tls, and more. Today, Bun's CLI gets
over 22 million monthly downloads, and tools like Claude Code and OpenCode depend on it
as their runtime. Vercel, Railway, and DigitalOcean all have first-party Bun support.
Bun's stability challenges stemmed from a fundamental tension: JavaScript is garbage-collected, and Zig (like C) does not manage memory for you. Mixing these two paradigms at scale is rare enough that no language really designs for it. The result was a steady stream of memory bugs that no amount of tooling could fully prevent.
Here's a small sample of bugs fixed in Bun v1.3.14:
node:zlib when calling .reset() on a stream while an async .write() was in progressnode:http2 when re-entrant JS callbacks triggered hashmap rehashingUDPSocket.send() where valueOf() callbacks detached ArrayBuffers mid-sendtlsSocket.setSession() — each call leaked one SSL_SESSION (~6.5 KB)fs.watch() watchers were never GC'd after .close()background-clip and multi-layer backgroundsMessageEvent where GC marker thread observed torn variants during concurrent accessUDPSocket.sendMany() when connection state changed mid-iterationcrypto.scrypt — callback and buffers never released on allocation failureDuplexUpgradeContext — full leak per tls.connect({ socket: duplex })The team was already doing more than most projects: patched Zig with Address Sanitizer support, ship ReleaseSafe builds on Windows, fuzz with Fuzzilli 24/7, and run comprehensive memory leak tests. But as Jarred put it: "I was tired of going to sleep worrying about crashes in Bun."
The decision to port to Rust — rather than C++ (which was already 20% of Bun's codebase) — came down to one thing: compile-time guarantees over style guides.
In Zig, cleanup relies on defer and errdefer at each call site.
In Rust, the Drop trait runs automatically when a value goes out of scope.
The difference is profound: Zig requires you to remember to add cleanup code;
Rust ensures it runs every time. For a codebase mixing GC-managed and manually-managed
memory, this systematic guarantee eliminates entire categories of bugs.
| Language | Cleanup Mechanism | Enforcement | Stability Impact |
|---|---|---|---|
| Zig | defer, errdefer |
Manual per call site | Easy to forget or double-clean |
| C++ | ~Destructor, &&Move | Automatic via RAII | Better, but still memory-safe gaps |
| Rust | Drop |
Compiler-enforced via borrow checker | Use-after-free and double-free are compile errors |
Beyond Drop, Rust gives Bun's team: the borrow checker
(compile-time memory safety), Miri (experimental interpreter for detecting
undefined behavior), and LeakSanitizer (tracking all native code memory
allocations). These tools run in CI and catch issues before they ever reach production.
Jarred Sumner's original plan was to add Rust-inspired smart pointers to Bun's Zig codebase. But after thinking about the ergonomics, he decided to spend a week testing if Anthropic's new model could rewrite Bun in Rust. A few days in, a high percentage of the test suite started passing, and the opinion shifted from "this is worth trying" to "I'm going to merge this."
Before writing any code, Jarred spent about 3 hours discussing with Claude how to map
patterns from Zig to Rust. This was serialized into a PORTING.md document
that later ended up on Hacker News. Then, a dynamic workflow analyzed every struct field
in the codebase, traced its control flow, proposed lifetimes, and had 2 adversarial reviewers
check each proposal. The result was a LIFETIMES.tsv — a structured lifetime
specification for the entire codebase.
The core insight behind the port is treating engineering work as a loop:
About 50 dynamic workflows ran continuously over 11 days. Each workflow was a loop for a
specific purpose: generating porting guides, mechanically translating .zig files
to .rs files, fixing compiler errors, getting subcommands to work, and running
the test suite. Jarred monitored the workflows — reading outputs to check for issues and
prompting Claude to edit the loops when things went wrong.
The most innovative part of the approach is the split-window adversarial review pattern. For every code translation task:
This split prevents the confirmation bias that occurs when the same agent that wrote the code reviews it. The reviewers caught real bugs that compiled clean and looked correct:
Box<uv::Pipe> was dropped at the end of a match arm, but uv_close is asynchronous — libuv kept the raw handle pointer. The fix: Box::leak(pipe) to let libuv own the memory until the callback fires.t.trunc() on negative times (pre-1970 file mtimes) produced negative nanoseconds — an invalid timespec. The fix: t.floor() to keep nsec in [0, 1e9).unwrap_or(1.0 - second.percentage.unwrap()) evaluates the argument eagerly — if first.percentage was Some, it still panicked. The fix: unwrap_or_else with a closure.
At peak, the rewrite ran 16 Claudes per workflow, across 4 separate git worktrees — 64 total.
The first attempt failed because Claudes were stepping on each other with git stash
and git reset. Jarred added a workflow rule: no git commands that don't commit
a specific file at once. No cargo either. No slow commands at all.
At peak throughput, Claude wrote about 1,300 lines of code per minute. Every line was reviewed by two adversarial reviewers before committing. The busiest hour saw 695 commits.
After writing all the code, the next challenge was fixing ~16,000 compiler errors.
The approach was beautifully simple: treat every compiler error as a task in a work queue.
cargo check wrote errors to a file, grouped by crate. 64 Claudes divided them
across 4 worktrees — each one fixing, two reviewing, one applying.
The trickiest errors were cyclical dependencies. The Zig codebase was one compilation unit (effectively one crate), but the Rust codebase needed ~100 crates for faster compilation. A workflow classified where cyclically-dependent code should go, then another workflow performed the refactor. This revealed the ~16,000 errors — a massive number for one human, but manageable for 64 Claudes.
Several false starts required prompt engineering adjustments:
grep command froze disk reads for minutes. The fix: increase the default IOPS limit.next dev with HMR checked 100 times) timed out in debug builds. The fix: use systemd-run (cgroups) to limit memory, CPU, and isolate PID namespaces.
Bun v1.4.0 fixes 128 bugs that reproduce in v1.3.14 — from memory leaks and crashes to
miscolored help text. The most significant is the Bun.build() memory leak:
| Builds | Bun v1.3.14 (Zig) | Bun v1.4.0 (Rust) |
|---|---|---|
| 500 | 1,914 MB | 526 MB |
| 1,000 | 3,506 MB | 586 MB |
| 1,500 | 5,097 MB | 608 MB |
| 2,000 | 6,745 MB | 609 MB |
In v1.3.14, every Bun.build() leaks ~3 MB — tools like dev servers that
bundle on every request eventually run out of memory. In v1.4.0, memory levels off at
~600 MB. A previous attempt to fix this in Zig was not merged because the lack of
an equivalent of Drop made it too risky.
The Rust rewrite, combined with ICU changes and Identical Code Folding, shrank Bun's binary by ~20%:
| Platform | Bun v1.3.14 (Zig) | Bun v1.4.0 (Rust) | Reduction |
|---|---|---|---|
| Linux | 88 MB | 70 MB | ~20% |
| Windows | 94 MB | 76 MB | ~19% |
| macOS | — | — | -5.5 MB (initial) |
The size reduction came from: reduced Zig comptime usage, linker-level
Identical Code Folding, removing unused ICU data, and lazily decompressing small ICU
parts with a zstd dictionary.
Rust's support for cross-language LTO between C/C++ and Rust enables inlining across programming languages — a capability Zig didn't have. The benchmarks (Linux x64, EC2 Xeon Platinum):
| Benchmark | Bun v1.3.14 | Bun v1.4.0 | Δ |
|---|---|---|---|
| Bun.serve (HTTP) | 169.6k req/s | 177.7k req/s | +4.8% |
| node:http | 103.8k req/s | 108.5k req/s | +4.5% |
| Elysia | 158.9k req/s | 163.3k req/s | +2.8% |
| next build | 13.62 s | 13.03 s | +4.5% |
| vite build | 1.69 s | 1.65 s | +2.2% |
| tsc -b --force | 0.94 s | 0.89 s | +4.7% |
Rust's LLVM IR emits llvm.lifetime.start and llvm.lifetime.end
intrinsics for stack variables when they're no longer in use, allowing LLVM to reuse stack
slots. This is a significant win for recursive-descent parsers (TOML, JSON, YAML, JavaScript,
TypeScript). Previously, the team had to manually refactor large functions into many smaller
ones to work around an open Zig issue.
The rewrite introduced 19 known regressions, all since fixed. Most came from code that was syntactically identical in both languages but semantically different:
assert() runs its argument in every build. Rust's debug_assert! erases the expression in release builds, including an insert_stale call that broke HMR.bytemuck::cast_slice panicked on it. Blob.text() on UTF-16 BOM + odd bytes crashed. Fix: &buf[..buf.len() & !1].ReleaseFast removes bounds checks; Rust's release builds keep them. A port placeholder lowered the filename interning ceiling from 8.4 million to 270,272 — real projects hit it.comptime parameters process format strings before argument substitution. Rust needed a macro: bun_core::pretty!.Claude Code v2.1.181 (released June 17, 2026) and later versions already run on the Rust port of Bun. Startup time dropped 10% on Linux (from 517ms to 464ms) — barely noticeable, which is exactly the point. "Boring is good" when it comes to runtime rewrites.
Prisma Compute launched its public beta on Bun's Rust rewrite. Alexey Orlenko of Prisma reported: "We ran into memory leaks and a connection pool that couldn't recover after a VM was paused and resumed. When the Rust rewrite appeared, we tested it against the same failure modes. It handled them perfectly."
The Bun Rust rewrite is a landmark in AI-assisted software engineering. The numbers speak for themselves:
The realistic alternative was to do nothing — keep fixing bugs forever. Instead, Bun emerges with a codebase that has systematically better stability guarantees. As Jarred put it: "One engineer can do a lot more today than a year ago."
This is the same pattern we saw with the React Compiler Rust port, where AI generated most of the 435 commits for the TypeScript-to-Rust translation of a production-grade compiler. The difference is scale: the Bun rewrite used 64 concurrent agents with adversarial review, while the React Compiler port used a more conventional AI-assisted workflow. Both demonstrate that AI-assisted code porting is viable for core infrastructure — and both point to a future where language migration of large codebases becomes an engineering process design problem rather than a manual translation problem.
For related context on how AI is transforming the JavaScript ecosystem, see my analysis of the Fable 5 open-source ban controversy, which touches on the same themes of AI-generated code at scale.
The JavaScript ecosystem is evolving faster than ever — from AI-assisted rewrites of core runtimes like Bun to the expanding Rust tooling landscape. Navigating these changes requires a developer who stays current with the ecosystem and understands how to choose the right tools for your specific project.
I'm a full-stack web developer with 20+ years of experience building production applications. Whether you're evaluating Bun for your project, migrating an existing codebase, or need guidance on the modern JavaScript ecosystem, I'm happy to help. Reach out to me for a free initial consultation.
I also write in-depth technical articles on the tools and frameworks that matter — follow my articles for the latest in web development.
Need a web developer who stays on top of the ecosystem? Tell me about your project — I'll provide honest advice and a preliminary estimate. Free of charge.