← All posts
Interactive Report · Bun v1.4 · Released 2026-08-20

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]

2,900+GitHub issues fixed
+1,517newly passing Node tests
5×less idle CPU
−35%memory (up to)
−50%Linux startup (up to)

Headline numbers from the official announcement.[source]

01 · Release

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]

100 days, compressed2,036 commits2026-05-12 → 08-20
May
commits
266

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.

Jun
commits
353

240 touched .rs · 6 touched .zig. The shared allocator (#34009) landed, and the port began rolling out to Claude Code internally.

Jul
commits
752

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."

Aug
commits
665

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]

02 · Why Rust

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]

Zig code 535,496 lines[source] monthly downloads 22,000,000+[source] SSL_SESSION leak ≈6.5KB/call[source] first Zig code 2021-04-16[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]

PR #30412+1,009,257 / −4,024
files 2,188 issue comments 708 inline reviews 420 binary −3~8MB
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]

03 · The Port

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]

The review pipeline, in four stages

① Prep

prep 3 hours trial 3 files target 1,448 .zig files workflows ≈50 duration 11 days

[source][source][source]

② Parallel execution

4 worktrees × 16 Claudes ≈ 64 roles 1 impl + 2 reviewers + 1 fixer

[source][source]

③ Output

compiler errors ≈16,000 commits 6,502 peak 695/hour peak 58/minute peak ~1,300 lines/min

[source][source][source]

④ CI finish line

failing files 972 → 23 (2 days) platforms 6/6 green build #54202

[source]

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]

Test volume per platform (at merge)
Platformexpect() callstestsfiles
Debian x641,386,82660,6244,174
macOS arm641,259,95358,8504,175
Windows x641,007,54457,3374,173

tests skipped or deleted = 0.[source]

04 · After the Merge

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
files 285+5,004 / −1,822issue 8inline 36

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
deny lints 33violations 2,735files 1,036+18,626 / −23,043inline 139

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
files 42+486 / −145issue 24inline 142
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
files 17+1,113 / −153issue 16inline 36
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]

05 · Production

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 metrics, before / after

production CPU p99

Bun 1.3
24%
Bun 1.4
10%

production CPU p50

Bun 1.3
5.8%
Bun 1.4
2.5%

Idle CPU on a hello-world server fell 5×.[source]

06 · bun install

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]

Install scenario benchmarks
bun 1.4
1.41s · 376MB
npm
18.1s · 503MB
pnpm
13.5s · 1.8GB
yarn
20.5s · 498MB

First install on a cold cache.[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]

2,259 entries 0.85 → 0.33s 6,425 entries 5.5 → 0.85s trade-off +1.9ms / +3.9%
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]

07 · Node Compatibility

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]

Node.js v26 test suite+1,517newly passing
trace_events
30/30
sqlite
18/18 files
quic
235/237
fs
349/358
http
403/415
vm
98/101
stream
254/262
cluster
85/88
http2
261/280
tls
191/224
worker
116/157
diagnostics
36/69
test_runner
28/81
inspector
22/110

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]

08 · Frontend Pipeline

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]

stream · clone · build · test

download throughput (64MB · 4KB chunks)

Bun 1.4
1,519 MB/s
Deno 2.9
530 MB/s
Node 26
204 MB/s

subprocess pipeline / peak RSS

Bun 1.4
751 MB/s · 65MB
Bun 1.3
505 MB/s · 207MB
Node
256 MB/s · 106MB
Deno
170 MB/s · 114MB

[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]

09 · Zero Dependencies

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.Image 1.38× / 1.19× Bun.XML ≈5× @200KB wrapAnsi up to 88× stringWidth CJK 7–56× WebView demo 5 awaits
Stated hazard markers · 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]

10 · Footnotes

Footnotes: 14 stories hiding between the lines

1 · The port was so invisible he had the binary inspected

After days of running Claude Code on it, Jarred said "I can't tell a difference", then had Claude inspect the binary to confirm it wasn't secretly the Zig build.[source][source]

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]

11 · Making-of

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.

← All posts