Hi, this is Gergely with a bonus, free issue of the Pragmatic Engineer Newsletter. In every issue, I cover Big Tech and startups through the lens of senior engineers and engineering leaders. Today, we cover one out of four topics of last week's The Pulse issue. Full subscribers received the article below seven days ago. If you’ve been forwarded this email, you can subscribe here.
Last week in San Francisco, I met Jarred Sumner, creator of JavaScript runtime, Bun, and was keen to learn more about the rewrite of Bun from Zig to Rust. But at the time, Jarred didn’t want to say too much, as the tool used for the migration, Fable, was out of action due to the US government imposing export controls.

Jarred and I at Anthropic’s HQ, last week
Fortunately, the situation is now resolved and Fable is available globally, and Jarred has published a detailed post about the project. Before we get into the migration, some context:
Bun is a complex project, with lots of production software depending on it. Bun itself does many things:
- JavaScript, TypeScript and CSS transpiling, minifying and bundling
- A test runner
- A package manager (npm-compatible)
- Other things: module resolution, a WebSocket client, Node.js implementations and many modules
Today, Bun has 22 million monthly downloads, and software like Claude Code and OpenCode depend on it, while hosting providers like Vercel, Railway and DigitalOcean do first-party support for Bun.
Why a rewrite?
Zig is not a memory safe language, and memory-related bugs occurred continuously. Jarred lists memory-related bugs in the latest version of Bun: memory leaks, crashes due to memory issues, heap-out-of-bounds writes, and so on. This was after the Bun team patched the Zig compiler to reduce memory-related issues, and put end-to-end memory leak tests in place. As Jarred says:
“Our bugfix list felt bad and I was tired of going to sleep worrying about crashes in Bun. I don't blame Zig for that - other users of Zig don't have the bugs we had, and mixing GC with manually-managed memory is an uncommon enough thing for software to need that no language really designs for it. (...)
For Bun, correctly handling the lifetimes of garbage-collected values and manually-managed values has been a major source of stability issues - most often small memory leaks and occasionally crashes. Every memory allocation has to be meticulously reviewed. Where do these bytes get freed? How do we ensure it only gets freed once? Did we check for JavaScript exceptions properly? Is this garbage-collected pointer visible to the conservative stack scanner? Is this garbage collected memory or manually managed memory?”
Moving to a memory-safe, yet performant language could eliminate such errors, and Rust is one such language that fitted the bill. Jarred:
“A large percentage of bugs from that list are use-after-free, double-free, and "forgot to free" in an error path. In safe Rust, these are compiler errors and RAII-like automatic cleanup with Drop. Compiler errors are a better feedback loop than a style guide.”
However, doing a full rewrite on Rust has always been a terrible idea. Or at least, it used to be, because of how unbearably long it would have taken:
There are two problems with rewrites: they take too long, and they take waaaay too long. A dev who has done rewrites probably knows how things tend to go:
- Make an educated guess about how long it will take; say, nine months.
- Nine months later, there’s still another ~6 months to go because new functionality is added to the original codebase, and now that new functionality needs to be added in!
- By 15 months in, there’s still months left to go for the same reason!
- In the end, you manage to mandate a “feature freeze” for two months and finish the rewrite in ~18 months, if lucky. The original nine-month estimate can end up taking 2+ years.
Jarred likened rewriting Bun in Zig to this:
“Historically, rewrites are a terrible idea. Excluding comments, Bun is 535,496 lines of Zig.
A rewrite in another language would take a small team of engineers a full year.
A year of zero user-facing impact is not a realistic option we could consider. So, enforcement through code-style to fix stability issues was our best bet, and was our plan when we added Rust-inspired smart pointers to Bun's codebase.
But honestly, I didn't want to do it. Homegrown smart pointers offer worse ergonomics than Rust, with none of the guarantees.
What if, instead, I spend a week testing if Anthropic's new model [Fable] can rewrite Bun in Rust?”
Rewriting Bun with Fable
Unsurprisingly, the rewrite was not as simple as typing a prompt like: “Claude, rewrite Bun in Rust. Make zero mistakes.” Instead, this is how Jarred did it:
Step #1: Prep work. Three hours of intense prep work with Claude, explained Jarred:
“Before writing any code, I spent about 3 hours talking to Claude about how to map patterns from our Zig codebase closely to Rust. Claude serialized this discussion into a PORTING.md document, which ended up on Hacker News [as the Zig → Rust porting guide]”
This guide is a 600-line file with instructions like:
Ground rules:
- No tokio, rayon, hyper, async-trait, futures. No std::fs, std::net, std::process. Bun owns its event loop and syscalls. (Rust core/std slice, iter, mem, fmt, and core::ffi are fine — only the I/O-touching modules are banned.)
- No async fn. Everything is callbacks + state machines, same as the Zig.
- Borrow-checker reshaping is allowed. When matching Zig flow yields overlapping &mut, capture the needed scalar (.len(), index) into a local, drop the borrow, then re-borrow. Do NOT reach for raw pointers just to silence borrowck; leave // PORT NOTE: reshaped for borrowck so Phase B diff readers aren't confused.
It’s a series of instructions that makes sense to someone who’s expert in Rust. If you want to learn more, we cover Rust basics and why Rust is different, with Alice Ryhl.
Step #2: Trial run + adversarial review. Asking Claude to rewrite three files out of 1,448 total number of files. After the rewrite, Jarred ran two separate adversarial reviews with Claude to critique the result, in separate sessions than the one that Claude made the changes in.
Step #3: split up the work across 64 AI agents. Jarred split up the job so that agents worked on files independent from one another, in parallel.
Step #4: iron out issues with the run (~1 day). When Jarred attempted to run all this, agents kept getting in each other’s way:
“I asked Claude to loop the workflow on all 1,448 .zig files, and about 2 minutes in, one Claude ran git stash before committing. Another ran git stash pop. And then git reset HEAD --hard. They were stepping on each other! And if I put each Claude into a separate worktree, I would run out of disk space because Bun's git repository is too big and eventually the changes will need to be compiled and seen together.
So, I asked Claude to edit the workflow to instruct Claude to never run git stash or git reset or any git command that doesn't commit a specific file at once. No cargo either. No slow commands at all.
Then, Claude resumed the workflows. And it was working! Too slowly, so I split it into just 4 workflow shards each with their own worktree (4 worktrees total), each running 16 Claudes committing and pushing files.”
Step #5: have it run and wait ~2 days. The parallel agents went to work, and completed the rewrite of 535,496 lines of Zig code over the course of two days. Each commit was checked by two adversarial reviews, before being committed.
Step #7: fix ~1,600 compiler errors (~12 hours). The rewrite was completed, but nothing compiled. Going crate-by-crate (‘crate’ is Rust’s concept of a top-level compilation unit), Jarred had Claude fix compiler errors. This alone would be a herculean task for an engineer, but not for Claude:
“Fixing the cyclical dependencies revealed about 16,000 compiler errors. A massive number for 1 human, but not a crazy number for 64 Claude’s at once.
To maximize parallelism, the workflow looped over each crate.
- For each crate, run cargo check, group the output by file and save the errors to a file
- Fix all the compiler errors within that crate
- 2 adversarial reviewers for the crate's changes
- 1 fixer applies the fixes”

Visualizing fixing of errors, one by one, done by the agents. Source: Anthropic
The enjoyable thing about this phase of the migration was that the agents ran from midnight until 11:30am, fixing compiler bugs on their own – when Jarred and the team were getting some sleep.
Step #8: run tests locally (~2 days). Bun has a large test suite. The next step was to get these tests to run without compilation errors.
Step #9: get the test suite to pass CI (~3 days). Once the tests were running (and failing), the next step was to fix the code, so that the tests could pass. This took two days.
Step #10: Done in 11 days! After all the tests passed and Jarred verified that everything worked as expected, he merged the changes. The whole process took 11 days, from planning to the finish.

The rewrite: porting ~550K lines of code, in 6,500 commits, over 11 days, with 64 agents
How repeatable is this process?
The rewrite cost a whopping $165,000 with API pricing. With Fable’s API prices, the rewrite consumed 5.9 billion uncached input tokens, 690 million output tokens, and 72 billion cached input token reads. Anthropic sells API tokens at a margin as its business, so the cost of the rewrite for it was lower. It’s a large amount: the equivalent of the annual base salary for a software engineer at a mid-tier company in the US!
But then again, could have an engineer done all this work in a year? Probably not, and Mitchell Hashimoto says the same:
“On the cost, I think $165,000 at API pricing for Fable (didn’t verify) is an incredible deal. There’s absolutely no way an engineer with that salary would’ve been able to achieve the milestones Claude did in 11 days. No way. (Even if you break it down to N engineers paid $165K total in 11 days it doesn’t math out)
This does, however, also reconfirm my own biases which is that Fable in particular is most excellent at hard, focused tasks with clear reward functions. I’ve been tweeting about this recently.”
What if AI enables rewrites and migrations that wouldn’t have been considered before? The idea of rewriting Bun in Rust without AI was impractical, admits Jarred:
“By hand, I think this would've taken three engineers with full context on the codebase about a year, during which time we wouldn't be able to improve Node.js compatibility, fix bugs, fix security issues or implement new features. We never would've done that. The realistic alternative was to do nothing and keep fixing the bugs at the top of this post forever.”
A rewrite or migration taking months or years is why so many of these projects never happen. Let’s take aside the cost for a minute and consider this question: if AI can shorten a one-year rewrite to a week: would you do it?
If the answer is “hell, yes:” a blueprint now exists in the form of the Bun migration on how to do it. There are some caveats not detailed in the post, though:
- You need an engineer who is very motivated and knows the codebase very well
- You need an extremely robust test suite, so when the test suite passes, you know it works
- You need to be willing to invest a lot in tokens, not knowing how well it all will work
In fairness, #3 is the weakest point because we know LLMs are pretty good at “mundane” work like code migrations. With a good test suite (#2) and a motivated engineer to iron out things (#1), you’ll more likely than not succeed.
The remaining question is how much can be spent. It will likely not be $165K: and costs can be reduced with a simpler project, or by being thoughtful about model usage. For example, do high-level planning with the most expensive model, and cheaper ones for coding and review tasks.
Migrations with AI are surely speeding up, but only when projects are well-engineered like Bun’s has been.
Read the full issue of The Pulse this excerpt is from, or check out the latest The Pulse from today. Today’s issue covers:
- Grok’s CLI uploaded all your local files to the cloud, then got caught.
- New trend: concern about massive increase in code review load.
- Are more devs at enterprises upset about enterprise pricing by AI labs – and does it matter?
- Linux creator: AI “clearly useful.”
Subscribe to my weekly newsletter to get articles like this in your inbox. It's a pretty good read - and the #1 software engineering newsletter on Substack.