They rebuilt the runtime,
and users got quieter
A million-line Rust port sounds like it should change everything. For the people actually running Bun, Claude Code among them, almost nothing looked different. CPU, memory, startup time, and Node compatibility all improved at once, and the user base barely said a word.[source][source]
Headline numbers from the official announcement.[source]
The road to the release button: what followed "3 hours" became a new foundation
Bun 1.4.0 shipped on August 20, 2026. The official account kept the announcement to four words: "Bun 1.4 is here". Behind that line sat 2,036 commits since Bun 1.3.14. 1,375 of them touched at least one .rs file; only 21 touched a .zig file, and after July the Zig changes stopped entirely. Features came along for the ride, but the release itself was a swapped-out development substrate.[source][source]
227 touched .rs · 15 touched .zig. The Rust port (PR #30412) merged first; the big memory-leak sweep (#30875) and the Clippy lockdown (#31116) came after.
240 touched .rs · 6 touched .zig. The shared allocator (#34009) landed, and the port began rolling out to Claude Code internally.
460 touched .rs · 0 touched .zig. The Node compatibility sprint month: "we're merging some more node.js compatibility improvements and then will release bun v1.4."
448 touched .rs · 0 touched .zig. The pnpm-parity PR #38333, a slipped launch date, and finally "3 hours".
Commit triage: 1,375 touched .rs · 21 touched .zig · 19 both · 659 neither.[source] July remark[source]
For an infrastructure swap this size, the run-up was oddly routine. First the official account posted "Bun v1.4 is compiling". Two days later it slipped the date: "Bun v1.4 is delayed until Monday. Want to merge a few more things." On launch day Jarred Sumner wrote "things are uploaded"; his last post before the announcement read simply "3 hours". A million-line migration closed out the way most releases do — video edits, uploads, a few final merges.[source][source][source][source]
The scoreboard was anything but routine. The official summary ran: over 2,900 GitHub issues fixed, 1,517 more Node.js tests passing, idle CPU cut up to 5×, memory down as much as 35%, Linux startup as much as 50% faster — plus the rewrite from Zig to Rust itself. The team could have shipped the day the port compiled. They waited instead, holding the release until compatibility and operational performance reached the same baseline.[source]
That is why this report opens with a timeline. Set the Rust port at the origin and the rest attaches to it in order: the leak sweep, the shared allocator, the Node compatibility sprint, pnpm parity, the final delay. Bun 1.4 is what happened when follow-up work turned one enormous PR into a product.[source][source][source][source]
Why Rust: the kind of bug that "just be more careful" never fixes
Bun lives in two worlds at once: JavaScriptCore's garbage-collected heap on one side, native code managing its own lifetimes on the other. Jarred published the bug list that split produced. A use-after-free in node:zlib. Pointer invalidation inside node:http2. ArrayBuffers detached mid-UDP-send. A double-free in the CSS parser. GC root leaks in fs.watch(). tlsSocket.setSession() leaking roughly 6.5KB of SSL session per call. Zig is not the villain of that list. At Bun's scale, keeping the GC-versus-manual-memory boundary correct at every call site had come to depend on sustained human attention, and attention slips.[source][source]
Jarred never framed the move as a repudiation of Zig. Bun's first Zig code dates to April 16, 2021; the initial version took about a year to build, in a world before LLMs. The Bun CLI now clears more than 22 million downloads a month. "Zig made Bun possible" and "going to sleep worrying about crashes" come from the same essay. The old choice held. The project simply grew until its failure costs and feedback loops called for a different one.[source]
"Compiler errors are a better feedback loop than a style guide." · Jarred Sumner
Rust won on a single axis: when errors get caught. In safe Rust, use-after-free, double-free, and missed frees on error paths surface as compiler errors, or Drop cleans them up. Bun's Zig codebase ran to 535,496 lines excluding comments, so a traditional rewrite would have frozen features and security fixes for a small team for the better part of a year. They chose a mechanical port instead: move the existing Zig over to Rust with minimal behavior change, and leave the architecture alone.[source][source]
From the PR description: "No async rust."
No async rust.
The binary size shrinks by 3 MB - 8 MB
Nobody was adopting Rust-ecosystem conventions wholesale here. The existing event loop and architecture stayed put; the difference is that ownership and cleanup rules became visible to a compiler.[source]
Diff and conversation volume as reported by GitHub.[source]
How a million lines got reviewed: making Claude distrust Claude's own tests
"Jarred asked Claude to rewrite Bun in Rust" misses nearly everything that mattered. He kept roughly 50 dynamic workflows running for 11 straight days. The first three hours went into building PORTING.md, a map of Zig patterns to Rust patterns, and then he ran a trial on just 3 files before letting it loose on all 1,448 Zig files. In the main run, every implementing Claude got paired with two or more adversarial reviewer Claudes, and nothing was committed until review and fixes had cycled through.[source][source][source]
① Prep
② Parallel execution
③ Output
The most important design choice was splitting the context. The implementer saw the original Zig, the porting plan, and its own chain of reasoning. The reviewer saw only the diff, plus one instruction: assume this code is wrong and find out why. That split caught real bugs in code that already compiled — a use-after-free and a double-free after async uv_close, negative nanoseconds produced by trunc on pre-1970 timestamps, panics from an eager unwrap_or. Rather than let one model grade its own homework, the workflow built review streams with opposing interests.[source]
The sharpest question on the public PR
Just a heads up that it may be a good idea to review the tests to make sure Claude didn't change them to "fix" the problem
That warning came from @skyfallwastaken: the reviewer's charge was plain: Claude might have bent the tests until they passed, rather than fixed the bugs. It was the most legitimate question anyone could put to a million-line PR. Jarred wrote that, before merging, he went through and confirmed the existing tests were genuinely running on every platform, not being skipped. The final count of tests deleted or skipped: 0.[source][source][source]
The run was messy. Early on, different Claudes ran git stash, stash pop, and reset --hard over each other's work, clobbering it. The fix was structural: 4 worktrees with 16 Claudes each, peaking around 64 agents in parallel. Code came out at up to roughly 1,300 lines a minute, and the port branch accumulated 6,502 commits, merges excluded. The reproducible lesson isn't the generation speed. It's that broken working habits got repaired with prompts and workflow rules, then rerun.[source][source]
During compilation, roughly 16,000 errors became the work queue. In CI, failing files fell from 972 to 23 in two days, and the merge landed only after Buildkite build #54202 went green on all six platforms. On Debian x64 alone, that meant 1,386,826 expect() calls across 60,624 tests in 4,174 files, with macOS arm64 and Windows x64 each running over a million assertions of their own. No human read all million lines. Instead the team layered language-independent tests, isolated adversarial review, per-platform CI, and manual sampling.[source][source][source]
| Platform | expect() calls | tests | files |
|---|---|---|---|
| Debian x64 | 1,386,826 | 60,624 | 4,174 |
| macOS arm64 | 1,259,953 | 58,850 | 4,175 |
| Windows x64 | 1,007,544 | 57,337 | 4,173 |
tests skipped or deleted = 0.[source]
The merge was a starting line: the follow-up PRs that turned the port into a product
PR #30412 landing was less a finish line than the opening of a new development substrate. Days after the port, PR #30875 titled "Fix effectively every native-code memory leak in Bun": +5,004 / −1,822 across 285 files. Nobody claimed the Rust translation had made the leaks evaporate on its own. The new tooling and sanitizers surfaced real ownership bugs, and the team cleaned them out at scale.[source]
PR #30875: fix every native memory leak
The post-port deep clean, and the direct reason the repeated-build memory curve went flat.[source]
PR #31116: 33 Clippy lints sealed at deny
More lines deleted than added. The million-line port could have stopped at "it compiles". This PR turned the cleanup into build rules, so future Rust code can't quietly re-accumulate the same debt.[source]
PR #34009: JSC and Bun share one allocator
Bun and JavaScriptCore now share the same memory allocator
Before this, JavaScriptCore ran on libpas while Bun ran on mimalloc — two allocators side by side. In 1.4, JSC uses mimalloc too, gaining features like a scavenger that returns memory when idle. That change underpins the HTTP-server memory drop and the long-running CPU reduction.[source][source][source]
PR #31491: hot paths went to C++ SIMD instead
Make it faster than it was in Bun v1.3.14 (zig) and on main.
xxHash3 moved to C++ Highway SIMD with runtime dispatch, and Bun.stringWidth, HTML escaping, and ANSI scanning took C++/SIMD paths too. Rust owns the structure and the ownership rules; the CPU-bound kernels stay on C++ and SIMD. A hybrid, on purpose.[source][source][source][source]
Bun 1.4's technical identity is verifiable boundaries and fast kernels; Rust purism was never the point. PR #31116 captures the day-after mood best: the morning after pushing a million lines, the team spent the sprint clearing 2,735 lint violations with no new feature in sight.[source]
What users actually feel: why "I can't tell a difference" is the highest praise
Claude Code got the Rust port first. On May 21 Jarred wrote: "Been running Claude Code on the Rust port of Bun for the last few days. I can't tell a difference". He said it passed the entire suite on all platforms, and that he'd had to ask Claude to inspect the binary just to confirm he was really running the Rust port and not the Zig build by mistake. The follow-up landed on June 4: the port had rolled out to Claude Code internally the night before, and a full day went by without a single issue report.[source][source][source]
"otherwise, barely anyone noticed. Boring is good." · bun-in-rust#production
For a rewrite, "can't tell a difference" is about as close to a definition of success as there is. Claude Code has run the Rust port since v2.1.181. Linux startup came out 10% faster, and almost nobody registered it. Prisma Compute aimed the port at its own known failure mode — connection pools and memory leaks that never recovered after a VM pause and resume — and reported that the Rust build handled it correctly. The language underneath changed; the product on top just got quieter.[source]
production CPU p99
production CPU p50
Idle CPU on a hello-world server fell 5×.[source]
peak RSS, Bun 1.3 → 1.4 (MB)
Bar length shows the relative 1.4 value. HTTP server memory fell 13~48% versus 1.3.[source]
Memory after building the same 60-module project in-process 2,000 times
| builds | Bun 1.3.14 | Bun 1.4.0 |
|---|---|---|
| 500 | 1,914MB | 526MB |
| 1,000 | 3,506MB | 586MB |
| 1,500 | 5,097MB | 608MB |
| 2,000 | 6,745MB | 609MB |
1.3.14 leaked roughly 3MB per call. Next.js App Router SSR settled at 238MB after 4,000 pages, against 410MB for Node under the same conditions. For anyone running this in production, the curve going flat matters more than any "up to 35%" headline.[source][source]
hello.js startup time
peak memory
The port was never sold as a stability play that tolerates slower numbers. Stability and resource use improved in the same release.[source]
bun install, rewritten again: from a fast package manager to a team tool
There's more to the package manager in Bun 1.4 than a benchmark win. The benchmark still comes first. On the T3-stack Next.js example (25 direct dependencies, roughly 220 packages in the lockfile):[source]
First install on a cold cache.[source]
Fresh checkout with a warm cache.[source]
Reinstall with nothing changed.[source]
The bigger change is PR #38333, and the title says it all: dedupe, prune, pm licenses, audit fix, --filter, --catalog, nested overrides, transitive updates, and workspace fixes, all in one PR. GitHub counts 390 files at +107,650 / −7,700, with 65 inline review comments. The PR describes roughly 1,100 new or ported tests and differential testing against pnpm across 26 update scenario families. Jarred put the goal in one line: "Brings Bun's package manager to parity with pnpm for monorepo workflows".[source]
The same PR rebuilt performance into the structure instead of layering it on top. The isolated linker used to rescan every store entry each time one finished; that became a per-entry intrusive waiter list. On a 2,259-entry repro, main-thread link CPU dropped from 0.85 to 0.33 seconds. On a synthetic 6,425-entry monorepo, from 5.5 to 0.85. The trade: no-op isolated installs got 1.9ms (3.9%) slower because links are rechecked. Both of Jarred's lines hold side by side — "the removed work grows quadratically", and the defense that "Everything added is otherwise off the plain-install path".[source]
From the review: lockfile versions, and "do it in this PR"
I think we can keep it at v2 because we haven't shipped v2 in a prod release · Dylan Conway
Do not leave open as follow ups. Do it here. In this PR.
what are some untested edgecases? add more tests.
That exchange says more about the team's 1.4 baseline than any feature list could. Compatibility work didn't get pushed to follow-ups; even the monorepo root/member/catalog combinations were pulled into this PR's scope.[source][source][source]
From the bug tracker: "N packages installed", except only one was
Trust in a package manager comes down to whether the success message tells the truth. The bug behind PR #35426: installing N git dependencies that shared a single repository actually installed only one of them, printed N packages installed, and exited 0. Rerunning recovered about one package per attempt. The fix drains the shared clone task's full callback queue and resolves late duplicate arrivals against already-completed checkouts. Killing a false success beats making installs faster, and this PR shows that priority in practice.[source]
Node.js compatibility became a per-commit scoreboard, not a feeling
The old compatibility argument ran on anecdotes — this npm package works, that one breaks. Bun 1.4 replaced it with Node.js's own test suite: 1,517 tests newly pass, and the suite runs on every commit. node:http, node:fs, node:cluster, node:timers, node:zlib, node:vm, and node:stream each sit around 97% of Node's tests. node:quic reached 99%. node:events, node:trace_events, and node:sqlite cleared 100%.[source]
The chart keeps the strong modules and the lagging ones side by side. node:sqlite: 18 files, 319 subtests, 0 failures, 4 skips.[source][source]
The individual modules show how those numbers were earned. For node:http, the team threw out the old fetch-based shim and ported Node's _http_client.js directly, layering it over net/tls sockets, the Node HTTP parser, and Agent pools. The commit message records the Node HTTP suite climbing from about 55% to 82.3%, with 182 vendored tests added.[source]
The newer modules went further than API surface. node:vm climbed from 72% to 97%; worker_threads moved from about 37% to 73.9%. Outside the repo, the scoreboard turned into ecosystem support: Playwright, Next.js 16, Vitest coverage, OpenTelemetry, and Datadog profiling now run on Bun.[source][source][source][source][source]
The page never claims "100% compatible". Inspector sits at 22/110, the test runner at 28/81, diagnostics_channel at 36/69. Keeping strong and weak modules on the same screen matters: it lets users judge whether the compatibility their stack needs has actually arrived.[source]
The frontend pipeline speeds up on three fronts: streams, builds, and tests
For web applications, the most direct change in Bun 1.4 is Web Streams going native. A download pipeline moving 64MB in 4KB chunks hit 1,519MB/s on Bun 1.4 — 204MB/s on Node 26, 530MB/s on Deno 2.9. The subprocess pipeline climbed from 505MB/s in Bun 1.3 to 751MB/s, and peak RSS fell from 207MB to 65MB. Every number comes from the same script run on each runtime.[source]
download throughput (64MB · 4KB chunks)
subprocess pipeline / peak RSS
Response.clone(): reading both sides of a 64MB streaming body
clone chain: depth 100 · body 10MB
Branches share chunks by reference, so cloning a body never duplicates the underlying data.[source][source]
code splitting: 20,000-module diamond DAG
React Compiler: about 860 components
full --compile
The React Compiler runs inside Bun's own parser, so there's no separate Babel/SWC parse-print round trip.[source][source]
bun test: the same 24 files
Slow files (e.g. sql/postgres.test.ts at 1.8s) get scheduled first; each worker grabs the next file as soon as it frees up; coverage and JUnit output get merged. --changed walks the import graph backward from the git diff to surface only the affected tests.[source][source][source]
Response.clone() is a practical win for frontend servers and proxies. In builds, the gap widened with scale. The test runner now splits work across files, isolates each shard, and schedules by the timings it recorded on your last run — then, when the code changes, it looks at which tests those changes actually touch.[source]
The closing scene: Bun is becoming a runtime that deletes dependencies
Read top to bottom, the new APIs in Bun 1.4 can look scattershot. Bun.Image, Bun.WebView, Bun.markdown, Bun.cron, Bun.Terminal, then JSON5, JSONL, JSONC, XML, TOML, Archive, and a set of ANSI string utilities, all landing at once. One thread ties them together: take the small tools and native addons every JavaScript project installs on day one, and fold them into vetted, built-in runtime features.[source]
15 dependencies · now built in
package.json · dependencies
- sharp
- puppeteer
- marked
- node-cron
- node-pty
- concurrently
- npm-run-all
- serve-static
- json5
- fast-xml-parser
- tar
- string-width
- slice-ansi
- cli-truncate
- wrap-ansi
built into Bun 1.4
- Bun.Image
- Bun.WebView
- Bun.markdown
- Bun.cron
- Bun.Terminal
- bun run --parallel
- Bun.serve routes
- Bun.JSON5
- Bun.XML
- Bun.Archive
- Bun.stringWidth
- Bun.sliceAnsi
- Bun.wrapAnsi
In Bun's own interactive demo, 15 dependencies get struck through one by one until the counter lands on "0 dependencies left".[source]
For frontend engineers, the emblem is Bun.WebView. On macOS it drives the system WebKit; across macOS, Linux, and Windows it can also steer an installed Chrome, Chromium, or Edge over CDP. Navigate, click, evaluate, screenshot — the whole demo runs in 5 awaits, and click events arrive carrying isTrusted === true. This goes further than replacing Puppeteer or Playwright: browser automation is now a runtime primitive.[source]
Bun.Image handles JPEG, PNG, WebP, GIF, and BMP through a sharp-like API with no native addon to install — and it beats the library it replaces: 1.38× faster converting a 1080p PNG to a 400×400 JPEG, 1.19× going from JPEG to WebP. That speed shows up across the new surface, too. On a roughly 200KB feed Bun.XML parses about 5× faster than fast-xml-parser or xml2js; Bun.wrapAnsi outruns the JavaScript implementation by as much as 88×; and on CJK text Bun.stringWidth lands anywhere from 7× to 56× ahead.[source][source][source][source]
Bun.markdown's HTML output is not sanitized, and HTTP/3 is not yet recommended for production. Fast defaults earn trust only when the hazard labels ship right beside them.[source][source]
The editorial takeaway should stop short of "you don't need npm packages anymore". The more accurate reading: Bun keeps narrowing the boundaries between runtime, package manager, bundler, test runner, browser automation, and image and document processing — widening the set of paths it can observe and optimize inside a single binary.[source]
"One engineer can do a lot more today than a year ago." · bun-in-rust#what-s-next
Ending this story at "AI wrote a million lines" loses the important half. One engineer wired together a test regime, role separation, a compiler, sanitizers, fuzzing, and real production canaries, then used that structure to steer a migration nobody would have attempted before. The verification structure built the product, more than the generation speed did.[source][source][source]
Footnotes: 14 stories hiding between the lines
1 · The port was so invisible he had the binary inspected
2 · The comment on the million-line PR: suspect the tests first
Just a heads up that it may be a good idea to review the tests to make sure Claude didn't change them to "fix" the problem
One sentence, and it captures the entire trust problem of the project.[source]
3 · The Claudes hit each other with git stash and reset --hard
The first failure of parallelization came down to a shared working directory and Git discipline. Model quality had nothing to do with it.[source]
3½ · A graph can't have that fight
Bun's parallelism first broke on a shared working directory, which is exactly the failure omo's dag rule forbids by construction: parallel lanes exist only where write scopes are disjoint and every node prompt stands alone. "dependsOn is ordering only."
4 · Code appeared at ~1,300 lines a minute, and none of it worked
The blog places that admission immediately after the generation-speed brag — exactly the right edit.[source]
5 · "If it needs a long explanatory comment, the code is wrong"
Claude kept producing stubs wrapped in long apologetic comments, so Jarred rewrote the review rules themselves.[source]
6 · Windows went green last
The Buildkite race flickered red and green per platform before finally settling at build #54202.[source]
7 · The tests were rough enough to kill isolation machines
Among them: maximal TCP socket exhaustion, gigabytes of disk I/O, and a test spawning about 10,000 processes.[source]
8 · The package manager that reported success and installed one package
Of N dependencies sharing one git repo, only one got installed — yet the output read N packages installed, exit code 0.[source]
9 · Patch files that matched in the first 16KiB counted as identical
PR #32749 closed the collision by switching cache identity to a SHA-1 over the whole patch.[source]
10 · A broken linker order file still built successfully
The failure mode was silence: no compile error, just about 12MB of Linux RSS quietly coming back.[source]
11 · Right after moving to Rust, hot paths moved to C++ SIMD
"Rust rewrite" never meant every last line would be Rust.[source]
12 · At one point the 'main blocker' for 1.4 was a video
Jarred wrote that the main blocker for the release carrying the Rust rewrite was recording the launch video.[source]
13 · The best production review was boredom
Claude Code's Linux startup got faster, and mostly nobody noticed. Jarred's verdict: "Boring is good."[source]
Behind this report: this article was itself built by an 8-node agent graph
Here is the part usually left out of a byline. This report was assembled by a mass-ulw run, DAG dag_f27ee8d6, 8 nodes. Five collection lanes ran as one parallel wave: blog digest, repo window stats, PR stories with review quotes, Jarred's X posts, and StyleGallery layout patterns. They fanned in to a Korean synthesis node, which fed a visual-engineering build node, which fed a final mechanical verification node. Fan-out, fan-in, verification wave. It's the same shape Bun's roughly 50 workflows and 64 parallel agents converged on over 11 days, except here it was declared up front instead of discovered by trial.
Failure showed up on schedule. Provider aborts hit mid-run, and nothing got rebuilt: no new run key, no fresh graph. The broken nodes were recovered in place, one through amend, others through revived children, while every finished node kept its cached result. Compare that with footnote 3½ above. Bun's Claudes fought over a shared working directory with git stash and reset --hard; a graph whose lanes have disjoint write scopes can't have that fight in the first place.
The doctrine extended to prose. The Korean edition's polite-tone rewrite ran as a 5-chunk parallel fan-out, each chunk gated by tag-parity checks so the markup came back intact. Split first, route second: the question is never "which model", it's "how do I make more small lanes."
Output stayed a claim until verified. The English edition went to an independent second model under a checklist loop; the first pass came back FAIL, targeted fixes were applied to exactly the failing items, and only a re-run earned SHIP. Same spine as "make sure Claude didn't change the tests", and same completion contract start-work enforces. The report wasn't graded by its own author.
Graph engineering is the repeatable part. mass-ulw is the spine expressed as a graph, hyperplan as an argument, start-work as a ledger: lanes that fail alone, outputs treated as claims, recovery that is surgical. That's why what took a company 11 days of false starts is now an evening's declared graph.
The whole graph is open source. Run the same pipeline yourself at github.com/code-yeongyu/oh-my-openagent.