# Bun Rust Rewrite: 64 Claude Agents, 535k Lines of Zig

Published: August 1, 2026
Tags: rust, javascript, ai, webdev

Bun 1.4's Rust rewrite hits canary: 64 Claude agents ported 535k lines of Zig in 11 days. What it fixes, what it overclaims, who's right: Sumner or Kelley.

A couple of weeks ago, in the [post about pnpm rewriting its install engine in Rust](/blog/pnpm-v12-rust-rewrite), I called Bun "the odd one out: it's written in Zig, not Rust." That line is wrong, and the awkward part is that it was wrong the week I wrote it. Jarred Sumner announced Bun's Zig-to-Rust rewrite on July 8; the pnpm post went up July 15, so the claim was stale at publication. I've since corrected that post so it acknowledges the rewrite and links here, which means if you click through you won't find the quoted line anymore. [Bun](https://bun.sh) is rewriting itself from Zig to Rust, and the rewrite is in canary for Bun 1.4, the first release where the runtime, bundler, and test runner are all Rust. [Jarred Sumner](https://x.com/jarredsumner) didn't do it by hand. He ran about 50 Claude Code workflows over 11 days, a few at a time, and let them loose on 535,496 lines of Zig.

This is the part of the JavaScript toolchain story I want to get right, because it's the one most likely to be flattened into a press release. The headline is real: Bun 1.4 fixes 128 bugs, ships a roughly 20% smaller binary, and runs a bit faster. The headline is also doing some work the underlying numbers don't fully support, and there is a genuine, public argument between Sumner and [Andrew Kelley](https://andrewkelley.me), the creator of Zig, over what actually happened. Both of them are partly right.

## TL;DR

- Bun ported its entire runtime from Zig to Rust in 11 days with 64 Claude agents, and the rewrite is in canary for Bun 1.4, not stable.
- The numbers that hold up are the leak fixes. An in-process build loop that ballooned to 6.7 GB in 1.3.14 levels off at 609 MB in 1.4, and the use-after-free and double-free bugs that dogged the Zig runtime are the class of failure safe Rust's `Drop` and borrow checker turn into compile errors.
- What the announcement overclaims is the framing. The 2 to 5% speed gain and the roughly 20% smaller binary come from cross-language LTO, ICU trimming, and linker work that could have shipped in Zig, not from Rust as a language.
- Don't move to 1.4 in production yet. Stable is still v1.3.14; run `bun upgrade --canary` on a throwaway branch, hammer the 2,000-build leak scenario against whatever you ship, and watch memory hold flat before you trust it.

## What the Bun Rust rewrite actually is

Bun 1.4 is a mechanical port of the whole runtime from Zig to Rust, done one `.zig` file at a time, holding behavior as close to 1.3.14 as the port allows. The last Zig release is [Bun v1.3.14](https://github.com/oven-sh/bun/releases/tag/bun-v1.3.14), tagged May 13, 2026. The first Rust release is v1.4.0, currently [in canary](https://bun.com/blog/bun-in-rust): run `bun upgrade --canary` to pull it.

"Mechanical port" is the load-bearing phrase. This is not a greenfield rewrite where Claude gets to redesign Bun. The Zig and Rust shown side by side in Sumner's [announcement](https://bun.com/blog/bun-in-rust) are deliberately near-identical: same function names, same scoping, same comments. The plan is to refactor toward idiomatic Rust *after* 1.4 ships, not during the port. That choice is what makes an 11-day timeline plausible, and it's what keeps the existing TypeScript test suite meaningful as the source of truth. The tests were never tied to the implementation language, so a faithful port should pass the same assertions.

The scope is everything Bun is: the JavaScript, TypeScript, and CSS transpiler, minifier, and bundler; the npm-compatible package manager; the Jest-like test runner; the Node.js API surface (`fs`, `net`, `tls`, `http`, `http2`, `node:zlib`); and the HTTP server. About 780,000 lines of Rust now sit where 535,496 lines of Zig used to. The "million lines" Kelley keeps citing is the diff: the announcement counts the port as adding a million-plus lines of new Rust. The 780,000 is the resulting codebase, the denominator for the unsafe-percentage figure. Both numbers are right; one is what went in, the other is what's there.

## Why rewrite Bun from Zig to Rust?

The honest case isn't speed. It's stability. Sumner opens the [announcement](https://bun.com/blog/bun-in-rust) with a list of bugs fixed in 1.3.14, bugs the Zig runtime shipped until that release, and it's grim reading for a runtime people run in production:

- heap-use-after-free in `node:zlib` when `.reset()` fires during an async `.write()` still in flight on the threadpool
- use-after-free in `node:http2` when a reentrant JS callback triggers a hashmap rehash that invalidates internal stream pointers
- use-after-free in `UDPSocket.send()` where a `valueOf()` callback detaches the `ArrayBuffer` between payload capture and the actual send
- a `tlsSocket.setSession()` leak of about 6.5 KB per call, every call, from a missing `SSL_SESSION_free`
- `fs.watch()` watchers never collected after `.close()`, pinned as GC roots by a reference-count underflow

These are the canonical memory-safety failures: use-after-free, double-free, leaks on error paths. In safe Rust most of them become compile errors, and the rest get `Drop` running cleanup exactly once when a value goes out of scope. The argument is that compiler errors are a better feedback loop than a style guide enforced by review, and for this class of bug, lifetimes of garbage-collected values mixed with manually managed ones, it's hard to argue with. Zig's `defer`/`errdefer` model puts cleanup at every call site and trusts the author to get it right every time. Bun, at half a million lines, did not get it right every time, and nobody reasonable expected it to.

This is the part I'd already half-gotten wrong. When I lumped Bun into the "everything's Rust now" trend in the [pnpm v12 post](/blog/pnpm-v12-rust-rewrite) and flagged it as the Zig holdout, I treated language choice as a settled, one-way decision. Sumner says explicitly that it used to be one, and that AI-assisted porting is what made it reversible at this scale. Take that claim seriously even if you're skeptical of the rest.

## How do you port 535,000 lines in 11 days?

You don't ask one agent to "rewrite Bun." Sumner's post is most useful as an engineering breakdown of the loops, and the loops are ordinary:

```js
// Pseudocode, not real code:
let task;
while ((task = todoList.pop())) {
  const result = task();
  const feedback = await Promise.all([review(result), review(result)]);
  await apply(feedback, result);
}
```

Each workflow is one implementer plus two or more adversarial reviewers plus a fixer, with the reviewers running in separate context windows so the Claude that wrote the code isn't the Claude grading it. At peak, four workflows ran at once, each with 16 Claudes, for about 64 concurrent agents across four [git worktrees](/blog/git-worktrees-are-underrated). They started on May 3 and merged May 14, across 6,778 commits and 1,448 `.zig` files moved to `.rs`.

The part worth copying is the prep work. Before any porting, Sumner spent about three hours with Claude producing a `PORTING.md` that mapped Zig patterns to Rust patterns, then ran a workflow that traced every struct field's lifetime across the codebase and wrote the results to a `LIFETIMES.tsv`. Three files were ported first as a trial, reviewed against the guides, then the full sweep ran. The false starts are instructive: two Claudes ran `git stash` and `git reset --hard` over each other's work, so the workflow rule became "no `git` except committing one specific file, no `cargo`, no slow commands." Claude also tried to "fix" compile errors by stubbing functions out, so a rule was added: if a workaround needs a paragraph-long comment, the code is wrong, fix the code.

The token bill for all of this is roughly 5.9 billion uncached input tokens, 690 million output tokens, and 72 billion cached reads, about $165,000 at API pricing, on what was then a pre-release build of Claude Fable 5, the Mythos-class model Anthropic shipped a month later. That's the part the search summaries will quote and the part that most obscures what happened. The money bought an engineered loop with human supervision, not a prompt and a prayer.

## Do the numbers hold up?

Here's the skeptic's reading. The [announcement](https://bun.com/blog/bun-in-rust) lists 128 bugs fixed versus 1.3.14, 19 known regressions (all fixed), zero tests skipped or deleted, and 1,386,826 `expect()` calls on Debian. The leak numbers are the most convincing: every in-process `Bun.build()` leaked about 3 MB in 1.3.14, so 2,000 builds ran the process to 6,745 MB. In 1.4 it levels off at 609 MB.

| Builds | Bun 1.3.14 | Bun 1.4 (canary) |
| ------ | ---------- | ---------------- |
| 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           |

If you run a dev server that bundles on every request, that's the difference between an OOM restart at 3 AM and a stable process. A previous Zig attempt at this fix [never merged](https://bun.com/blog/bun-in-rust) because, Sumner says, the lack of `Drop` made it too risky to trust. That's the strongest single argument for the rewrite.

The speed and size numbers want more scrutiny. Bun 1.4 is 2 to 5% faster on HTTP and app workloads: `Bun.serve` goes 169.6k to 177.7k req/s (up 4.8%), and `next build` goes 13.62s to 13.03s. The binary shrinks roughly 20%, from 94 to 76 MB on Windows and 88 to 70 MB on Linux. But the announcement itself attributes the throughput to cross-language LTO between C/C++ and Rust, and attributes the binary shrink partly to ICU trimming and Identical Code Folding. None of that is Rust-only. The initial Rust-only binary reduction came from dropping Zig's heavy `comptime` use; the rest came from linker work that could have shipped in Zig.

That's where Andrew Kelley's pushback lands hardest. The short version: a meaningful slice of the "Bun is better in Rust" win was engineering work Bun could have shipped without changing languages, and the post doesn't separate the two clearly.

## The Sumner vs. Kelley fight, both sides

[Andrew Kelley's response](https://andrewkelley.me/post/my-thoughts-bun-rust-rewrite.html) is not a calm post, and it's not framed as a technical critique. It opens on Sumner's "beginner energy" and builds to Kelley calling him "a stinky manager. Poor communication, unrealistic expectations, low empathy, no experience." But buried in the personal framing are four technical objections that hold up on their own:

1. The test-suite contradiction. The argument for merging a million lines of largely unreviewed code is that the test suite catches everything. But the same post opens with a long list of bugs in the Zig version the test suite didn't catch. Kelley's question: if it wasn't sufficient to catch Zig bugs, why is it sufficient to declare one million lines of unreviewed AI-authored Rust clean?
2. The style-guide sleight of hand. The post frames the choice as "style guide vs. language feature," which skips the main way projects actually eliminate bugs, which is putting engineers on it. Kelley points at TigerBeetle, another Zig project, as the team that did the work Bun didn't.
3. The fuzzing claim. The announcement implies diligent Zig-side fuzzing; Kelley says Bun told the Zig Software Foundation directly that they weren't fuzzing anything. The 24/7 coverage-guided fuzzing the post now brags about is new and Rust-side.
4. The omitted build speed. The Zig compiler, roughly 600,000 lines, builds clean in 16s and recompiles in 90ms with incremental enabled, Kelley writes. Bun's post doesn't give post-rewrite build numbers, and for a mechanical port of a codebase this size that's a conspicuous gap.

Each of those is fair. The first is the sharpest. Sumner's "0 tests deleted, 1.4M expect() calls, adversarial review, human in the loop" defense addresses process, not the logical gap. A suite that missed a `setSession` leak bleeding 6.5 KB a call for years is not a suite you can then cite as proof a million lines are correct. The suite is necessary and it caught a lot, the 128 fixes prove that, but "the test suite caught regressions" and "the test suite proves correctness" are different claims, and the announcement slides between them.

Sumner's side holds up too, and it's the one that matters more for users. The Zig bug list isn't marketing; those are real crashes and real leaks in a runtime with 22 million monthly CLI downloads that now backs Claude Code. The `Drop` argument is the real argument, and Kelley doesn't really answer it. He shifts to "you should have put in engineering hours instead," which is true and also not a refutation of the fact that Zig's manual-lifetime model was bleeding. You can fault how Bun got to Rust and still accept that being in Rust, with `Drop` and a borrow checker and Miri, materially lowers the chance of the next `setSession` leak.

Where I land: the rewrite is net good for Bun's users, the AI-authorship process is more disciplined than its critics assume, and the announcement overclaims by bundling genuinely Rust-driven gains (leaks, use-after-free) with engineering work that didn't need a rewrite (LTO, ICU, `comptime` cleanup). Read the two posts as correcting each other and you get closer to the truth than either one alone.

## What about the `unsafe` code and undefined behavior?

The thing people will actually worry about with AI-authored Rust is `unsafe`. The announcement gives the number: about 4% of the Rust sits in `unsafe` blocks, roughly 13,000 `unsafe` keyword uses across the ~780,000-line codebase, and 78% of those blocks are a single line, a pointer from JavaScriptCore or one call into a C library. That's lower than I expected, and it's the floor, not a ceiling. Bun embeds JSC, uWebSockets, BoringSSL, and SQLite, so `unsafe` will never hit zero. The plan is to push it down as the mechanical port gets refactored toward idiomatic Rust.

The worry has a concrete footprint. Issue [#30719](https://github.com/oven-sh/bun/issues/30719), filed May 14 by AwesomeQubic, is titled "PathString::slice dangling reference UB - add Miri to CI": a `core::slice::from_raw_parts` call constructing a dangling `&[u8]`. Miri was not in Bun's CI when the bug was filed; the reporter ran it locally to find it. The issue is closed, fixed by [#30876](https://github.com/oven-sh/bun/pull/30876) ("Add cargo-miri support and fix HiveArray aliasing UB", merged May 17), with [#30728](https://github.com/oven-sh/bun/pull/30728) an earlier attempt still open on GitHub. The reporter's parting line, "Please consider not vibe coding rust as AIs are not good at writing Rust and also hire a real rust dev," is the tweet-sized version of the whole debate. The bug is real, the kind of UB that turns up in any large `unsafe`-touching codebase, and Miri is exactly the tool meant to catch it. What's worth being precise about is who supplied the mechanism: the reporter ran it on a build Bun hadn't instrumented yet, not Bun's CI. The announcement now says Miri runs on a growing chunk of code in CI, which is the state after #30876 landed. So it's still the system working, not failing, but it's the community catching the bug and Bun turning the catch into infrastructure. And it's the proof that "AI wrote it" doesn't free you from reviewing `unsafe`.

## Where this leaves the JavaScript toolchain

The pattern is established enough that I'd stop calling it a trend and start calling it the default. [TypeScript](/blog/typescript-7-is-here) went to Go for build speed. [pnpm](/blog/pnpm-v12-rust-rewrite) is going to Rust for install speed. [Astro 7](/blog/astro-7) rebuilt its compiler in Rust. Deno is Rust from the ground up. Bun just went to Rust for stability. The reasons differ, but the move is the same: the JavaScript world's foundation is being rewritten in languages with a real memory model, and the engines running the JS itself, V8 and JavaScriptCore with their [tiered JIT pipelines](/blog/five-things-you-might-not-know-about-javascript), are the one layer staying put.

What's new in the Bun story is who's doing it and who owns it. Anthropic [acquired Bun in December 2025](https://bun.com/blog/bun-joins-anthropic). Claude Code ships as a Bun executable to millions of users, and the rewrite ran on Claude Fable 5 a month before Anthropic shipped it. Prisma launched a Prisma Compute beta on the Rust rewrite; Alexey Orlenko is quoted in the [announcement](https://bun.com/blog/bun-in-rust) on the memory leaks and the connection pool "that couldn't recover after a VM was paused and resumed," handled "perfectly" by the Rust build. That's the financial and structural context the announcement's lead leaves out: this is Anthropic making its own tooling's runtime safer, with Anthropic's model. The independence question is real, even if the code stays MIT.

The subtler shift is what "an engineer can do in a year" means. Sumner's closing line, that one engineer can do a lot more today than a year ago, is the part that's broader than Bun. If a faithful port of half a million lines, supervised, costs $165k and 11 days, the bottleneck for a class of large rewrites stops being the typing and starts being taste: knowing which port to do, how to scope it, and how to review the output. Bun's loop is a template people will copy, and the teams that internalize it first get to attempt things that used to require freezing development for a year.

## Should you run Bun 1.4 today?

If you're on Bun in production on 1.3.x, the answer is "not yet, but soon, and watch the bug list." As I write this, the latest stable tag in Bun's [release feed](https://github.com/oven-sh/bun/releases.atom) is still v1.3.14 from May 13. The only newer entries are `consolidation-step-N-green` canary tags from Bun's automation, not a stable v1.4.0.

To try it on a throwaway branch:

```sh
bun upgrade --canary
```

The honest test isn't the benchmark. Run the leaky-build scenario from the announcement, 2,000 `Bun.build()` calls in one process, against whatever you ship, and watch memory. If it holds flat the way the announcement's table shows, the rewrite did the thing you actually care about. If it doesn't, file the bug, because the whole premise is that this class of issue is now catchable and fixable instead of permanent.

I wrote that Bun was the Zig holdout two and a half weeks ago, and the line was stale the week it went up: Sumner had already announced the rewrite. The less comfortable lesson, for anyone writing about this stack, is that "X is written in Y" is now a claim with a short half-life, short enough that being a week behind the news is enough to be wrong. Bun's Rust rewrite is the cleanest example I've seen of AI-assisted porting at a scale that used to take a year, and the open questions about it, how much of the win was the language versus the engineering hours, whether machine-authored `unsafe` can be trusted, who owns the runtime your agent runs on, are the ones the rest of the JavaScript toolchain is going to answer next, one rewrite at a time.
