Bun 1.4 Runs on Rust Now. Read the Test Numbers Before You Switch
Bun 1.4 is the first release written in Rust after an 11-day port from Zig; here is what made that safe and how to read its Node.js compatibility numbers.
Umut Korkmaz
Published: 4 min read
On this page6
On August 20, the Bun team shipped Bun 1.4, the first release of the runtime written in Rust instead of Zig. It claims lower CPU, lower memory, faster startup and a big jump in Node.js compatibility. There are two lessons here: when a rewrite is actually safe, and how to read compatibility numbers before you move production traffic.
What shipped
The headline numbers from the release post:
- Idle CPU for a small hello-world app drops by 5x. Claude Code's production p99 CPU went from 24% to 10%.
- Peak memory for HTTP servers is 13% to 48% lower than on Bun 1.3. Fastify went from 233 MB to 120 MB, Express from 169 MB to 92 MB, over 1,000,000 requests with 64 connections. Node.js 26 sits at 156 MB and 145 MB on the same test.
- Startup is 2.5x faster on Windows (15.5 ms) and 2x faster on Linux (5.1 ms).
- Built-ins it rounds up, all first shipped in 1.3.x releases:
Bun.Image,Bun.WebViewfor headless browser automation,Bun.markdown,Bun.cronfor OS-level scheduled jobs, andBun.Terminalfor pseudo-terminals without node-pty.
How the rewrite was actually done
The details are in an earlier post, Rewriting Bun in Rust. The stated reason is a bug class: use-after-free, double-free and "forgot to free" on error paths, which come from mixing garbage-collected JavaScript values with manually managed memory. In safe Rust those become compiler errors or automatic cleanup through Drop. Jarred Sumner sums it up: "Compiler errors are a better feedback loop than a style guide."
Porting all 1,448 .zig files through to the merge took 11 days, May 3 to May 14. Sumner ran Claude Code dynamic workflows that paired one implementer with two or more adversarial reviewers whose only job was to find reasons the code does not work. At peak, about 64 Claude instances were running. Pre-merge cost was around $165,000 at API pricing, with 19 known regressions, each since fixed. The port is deliberately faithful; idiomatic refactoring comes after 1.4.
The agents made the speed possible, not the safety. Bun's own test suite is written in TypeScript, so it does not depend on the runtime's language. That gave the port roughly 1.39 million expect() calls across 60,624 tests on Linux as a fixed spec, with zero tests skipped or deleted. Merging to main was not a release either: the Rust build went out as canary, Claude Code has shipped on it since v2.1.181 in June, and only then did it become stable.
When a rewrite is justified
Most rewrite proposals come down to "the new language is nicer." Bun's case differs in four ways worth using as a checklist:
- A named bug class the target eliminates. Not "better code", but a specific category of defect the compiler will now reject.
- An executable spec independent of the implementation. If your tests are coupled to internals, they get rewritten with the code, and you lose the proof that behavior did not change.
- A production soak before the version number. Canary builds plus one large first-party consumer carried the risk before general users did.
- Port faithfully, refactor later. Mixing translation with redesign makes every diff unreviewable.
Generating code got cheap. Verifying it did not. The adversarial reviewers and the language-neutral test suite did the real work. I built Quorate, my CLI that runs several AI reviewers over a diff with deterministic gates, on the same bet: agent-written code is only as trustworthy as whatever checks it.
Reading the Node-compat numbers
Bun 1.4 adds 1,517 newly passing tests from Node's own suite. node:http passes 403 of 415, node:fs 349 of 358 and node:cluster 85 of 88. node:events, node:trace_events and node:sqlite are at 100%. The post says openly that Bun is not 100% compatible with Node yet.
97% is a good number, but it counts tests, not how much of that surface you use. The 12 failing node:http tests could be edge cases you never hit, or exactly what your proxy depends on. Before moving a service, I would check:
- Which
node:modules and native addons the service uses, and whether failing tests cover them. - Your own test and contract suites, run against a Bun-hosted instance, not just
bun testpassing on a laptop. - Behavior changes.
Bun.serveno longer serves sourcemaps for HTML routes in production. HTTP/3 inBun.serve()is experimental and not meant for production yet. Since 1.3.5, the default trusted-dependencies list covers only npm registry packages, so afile:,link:,git:orgithub:dependency needs its owntrustedDependenciesentry to run lifecycle scripts. - Memory under your own load. The 13% to 48% figure compares Bun 1.4 with Bun 1.3 on their benchmark, not your traffic, and not against Node.
My take
A production runtime switched languages, and on Claude Code barely anyone noticed. It worked because the project had a spec that did not care about the language and a real consumer to soak it. For the Fastify and Express services in my stack, the memory numbers are worth a trial. The right first step is one canary instance behind nginx with a rollback path, a full contract-suite run and a week of memory graphs. The test counts tell you where to look. They do not tell you it is safe.
Sources
Primary references used for this post.