Bun's Rust Rewrite: 535,000 Lines of Zig in 11 Days
Bun's maintainer explains how he used Claude to port 535,000 lines of Zig to Rust in 11 days, and why compiler-enforced memory safety beat a style guide.

Porting a 535,000-line runtime to a new language is supposed to take a small team a full year — which is why almost nobody does it. Bun’s creator did it in eleven days with AI writing the code, and the real subject of the post isn’t the speed. It’s the verification machinery that made a million machine-written lines safe to merge.
Source
By: Bun (Jarred Sumner)
Published: Jul 08 2026
Type: Engineering article
I saved this because it’s the most detailed public account of aiming a fleet of coding agents at a production codebase and shipping the result. The transferable part isn’t that a model can write a runtime — it’s the test-suite-as-specification trick and the adversarial review loop, which apply to any bulk code change, with or without a model.
What it took to rewrite Bun in Rust in 11 days
Context the post discloses in its first lines: Anthropic acquired Bun in December 2025, the author now works at Anthropic, and he used a pre-release Anthropic model for much of the rewrite. Keep that relationship in view while reading the results — the engineering is documented in unusual depth, but the narrator and the model vendor are related.
Bun is an all-in-one JavaScript runtime and toolkit: a JavaScript, TypeScript, and CSS transpiler, minifier, and bundler; an npm-compatible package manager; a Jest-like test runner; and a large slice of the Node.js API surface. Its creator wrote the first version alone in one year, in Zig, before LLM coding tools existed — and the post is explicit that Zig made Bun possible. Today the CLI sees more than 22 million monthly downloads, and tools like Claude Code run on it.
The problem was stability, and the post opens with receipts: a sample of bugs fixed in v1.3.14, almost all of them lifetime mistakes. Use-after-free crashes in node:zlib and node:http2. Leaks in crypto.scrypt, in TLS sessions (about 6.5 KB per call), and in fs.watch watchers. A double-free in the CSS parser. A race where the garbage collector’s marker thread caught a half-written value. The pattern matters more than the list: JavaScript memory is garbage-collected, the runtime around it manages memory by hand, and Zig asks you to write cleanup explicitly at every call site and get it exactly right on every error path — enforced mostly by review.
They had already done more than most projects: AddressSanitizer runs on every commit via a patched Zig compiler, safety-checked builds ship on Windows, Fuzzilli fuzzes the runtime APIs around the clock, and there are end-to-end leak tests. The remaining alternatives were style guides — TigerBeetle’s TigerStyle, Google’s 31,000-word C++ guide — or homegrown smart pointers, and both bottom out at enforcement depending on humans noticing. His summary: “Compiler errors are a better feedback loop than a style guide.” In safe Rust, use-after-free and forgot-to-free aren’t review findings; they’re build failures. And Drop, cleanup that runs automatically when a value goes out of scope, makes freeing the default instead of the discipline.
The strategy is where this stops being a normal rewrite story. The codebase is 535,496 lines of Zig, comments excluded, and a manual port would freeze bugfixes and features for a year. So the plan was a mechanical, transpiled-looking port with minimal behavior change — same architecture, same performance profile, refactor toward idiomatic Rust after v1.4. Two assets made that thinkable. First, Bun’s test suite is written in TypeScript, so it doesn’t care what language the runtime is; it became the specification, with roughly 1.4 million assertions across some 60,000 tests. Second, everything would be ported at once rather than incrementally — a lesson from hand-porting esbuild’s transpiler from Go to Zig years earlier, where going incremental would have littered the codebase with temporary code you hope to delete.
Then the run itself. Around 50 “dynamic workflows” — loops where an agent implements, others review, and one applies fixes — ran continuously for 11 days. The prep deserves a pause: a three-hour conversation with the model became PORTING.md, a Zig-to-Rust pattern map, and a separate workflow traced the intended lifetime of every struct field in the codebase into LIFETIMES.tsv, with both documents adversarially reviewed before any porting started. A three-file trial run came next, then all 1,448 .zig files ported across four git worktrees with 16 agents each — about 64 running concurrently at peak, producing roughly 1,300 lines per minute. Every line passed through two adversarial reviewers, and the post’s dry punchline matters: absolutely none of it worked yet.
Adversarial review is the load-bearing idea, so it’s worth stating plainly. The reviewer is a separate model context that sees only the diff — never the author’s reasoning — and is told to assume the code is wrong. It’s the same separation that makes human review work: whoever wrote the code wants it accepted. The post shows three real catches, all in code that compiled cleanly. A Box dropped before libuv’s asynchronous close callback ran, leaving the library holding freed memory — fixed by deliberately leaking it into the callback. trunc instead of floor, producing negative nanoseconds for pre-1970 file timestamps. And an eagerly evaluated unwrap_or argument that panicked while parsing color-mix() percentages.
Getting from “compiles” to “runs” used the compiler itself as the work queue. Splitting the single Zig compilation unit into about 100 crates surfaced cyclical dependencies and roughly 16,000 errors, which the workflows burned down crate by crate. The false starts are treated as process bugs rather than accidents: agents running git stash and git reset over each other’s work (fixed by banning git commands that don’t commit a specific file), agents “fixing” compile errors by stubbing functions out (fixed with a reviewer rule — “If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code.”), and test machines dying of disk exhaustion (fixed with cgroup isolation through systemd-run). When something goes wrong, fix the generator, not the output.
Then smoke tests — bun --version before bun test — sharded local runs, and the CI grind: 972 failing test files down to 23 within two days, all six platforms green by May 14. He manually verified the tests were actually running, not silently skipped, before pressing merge. The bill came to about $165,000 in tokens against an estimated engineer-year for three people — with zero tests skipped or deleted.
The honesty about regressions is unusual and worth as much as the wins: 19 known regressions, all fixed, mostly from code that looks identical across the two languages but behaves differently. Zig’s assert is a function, so its argument always runs; Rust’s debug_assert! is a macro that erases its argument in release builds, which silently dropped a hot-reload bookkeeping call. Zig’s ReleaseFast removed bounds checks that Rust keeps, so a ported off-by-one became a caught panic instead of silent corruption. About 4% of the new code sits in unsafe blocks, mostly single lines at the C and C++ boundary — JavaScriptCore stays embedded, so it will never reach zero.
The payoffs: v1.4.0 fixes 128 bugs that reproduce in v1.3.14, every instrumentable memory leak is fixed (2,000 in-process builds went from 6.7 GB of accumulated memory to a flat 609 MB), the binary shrinks by about 20% after related cleanup (94 MB to 76 MB on Windows), and throughput rises 2–5%, attributed to cross-language link-time optimization letting Rust and C++ inline into each other. Prisma’s Compute beta and Claude Code already run the port.
The quiet close is about the humans who maintain it: the ported Rust deliberately reads like the Zig it replaced, so the team’s mental map survives. Idiomatic Rust is a later, gradual project, with Miri, LeakSanitizer, and around-the-clock parser fuzzing as the new guardrails.
My take
What the source reveals, underneath the spectacle, is that this is a verification-capacity story rather than a code-writing story. The rewrite was thinkable only because the specification already existed outside the code — a language-independent test suite — and because review could be parallelized exactly the way authorship was. Writing stopped being the bottleneck; trusting became the bottleneck, and adversarial review is the mechanism that scaled trust.
The boundary condition matters more than the headline. The target was “identical behavior,” which is crisp and machine-checkable; a redesign with shifting semantics has no such oracle, and nothing here suggests agents would do well on one. I’d also weight two integrity numbers above every benchmark: zero tests deleted, and a manually verified test run before merge. The classic failure mode of big rewrites under deadline pressure is quietly weakening the tests — those two numbers are what make the rest credible.
Where this is useful
- Maintainers of large C, C++, or Zig codebases whose crash reports are dominated by lifetime bugs — the post shows exactly what moving that bug class into the compiler buys, and what it doesn’t.
- Anyone planning a cross-language port: the first project is making the test suite language-independent, because it’s the spec that makes “mechanical port” mean something.
- Teams using agents for bulk changes: copy the loop — one implementer, reviewers who see only the diff and are told the code is wrong, one fixer, and process fixes instead of hand-patches.
- Less applicable to greenfield design or behavior-changing refactors, where nothing like “the same million assertions must pass” exists to anchor review.
Apply this: Take your last month of crash reports and sort them by cause. If use-after-free, double-free, and error-path leaks dominate, that’s the specific signal Rust’s compiler addresses. Before any port, check the one dependency this method needs: can your test suite run unchanged against a new implementation?
What I want to remember
Remember: A model can write a million lines; it can’t be trusted with one. What made this merge safe wasn’t the model — it was a language-independent test suite treated as the specification, adversarial reviewers who never saw the author’s reasoning, and the discipline of fixing the process instead of hand-patching its output.