Bun 1.4.0 shipped on August 20 with its runtime source cut over from Zig to Rust. The release also presents fifteen package-shaped jobs as built-ins: image transforms, browser control, Markdown, PTYs, cron, archives, terminal text, and more. Those changes belong together. Bun is taking responsibility for more application behavior while using Rust, upstream compatibility tests, and production canaries to make that larger boundary safer. Try 1.4 as a pinned, measured migration. Do not swap it blindly for Node.
This is not a story about dependencies disappearing. A line can leave package.json while its
implementation, upgrade cadence, platform matrix, and security boundary move into the runtime. Bun 1.4 makes
that transfer unusually visible. The release compresses a JavaScript toolchain into one executable, then shows
the engineering bill for owning the result.
Fifteen dependencies did not disappear
Bun's release page covers everything shipped since 1.3.0 rather than only symbols first added by the 1.4.0 tag.
Feature headings carry version chips. Bun.Image first shipped in 1.3.14; parts of
Bun.Terminal date to 1.3.5; Bun.WebView spans 1.3.12 and 1.4.0. August 20 is still a
real boundary: 1.3.14 was the final Zig release, and 1.4.0 is the first stable Rust one.
Read the fifteen-to-one claim as product positioning, not interface parity. These are Bun-native ways to perform common jobs. They do not implement every package API, and adopting them trades portable npm contracts for a tighter runtime contract.
| Package-shaped job | Bun surface | What remains outside the line count |
|---|---|---|
| Sharp image work | Bun.Image | Native codecs, OS image facilities, platform-specific formats |
| Puppeteer browser work | Bun.WebView | System WebKit or an installed Chrome-family browser |
| Marked parsing | Bun.markdown | Application-owned sanitization for untrusted HTML |
| node-pty terminals | Bun.Terminal | POSIX PTYs or Windows ConPTY and their process semantics |
| node-cron scheduling | Bun.cron | Process lifetime or the operating system scheduler |
A dependency can leave the lockfile without leaving the system.
Integration can still earn the transfer. It can remove version skew, repeated parsing, process boundaries, install time, and adapter code, while giving one maintainer enough control to optimize an end-to-end path. Independent packages have strengths worth naming: their own release cadence, a public interface shared by several runtimes, and the option to replace one subsystem without replacing the runtime. Bun wins the trade only when its implementation and operating boundary are better for the workload.
A terminal call now ends inside the runtime
Bun.Terminal is the cleanest small example. A TypeScript program supplies dimensions and callbacks,
hands the terminal to Bun.spawn(), then reads and writes a pseudo-terminal without importing
node-pty. In the tagged 1.4 source, the native object stores the master file descriptors, reader,
writer, and event-loop handle. On Windows it also owns the ConPTY handle. The subprocess retains a pointer to the
attached terminal and calls back into it on exit. Bun coordinates both objects, so the PTY lifecycle becomes part
of the runtime lifecycle.
-
Create Bun.
Terminal Columns, rows, data and exit callbacksattach -
Spawn Child processChild process attaches to the PTYbridge
-
Own Native PTYPOSIX descriptors or Windows ConPTYschedule
-
Deliver Runtime eventsBun event loop returns data and exit
I downloaded the official macOS arm64 1.4.0 artifact, matched its SHA-256 to GitHub's release metadata, and
confirmed revision 34cbb9a40. A small PTY program spawned /bin/sh, received the marker
written by the child, and observed exit code zero. That reproduces the narrow path. It does not prove
node-pty parity, Windows ConPTY behavior, or resilience under an interactive agent's long session.
Built-in means Bun owns the JavaScript API and integration path. It does not mean Bun wrote every layer below it. JavaScriptCore, uSockets, BoringSSL, SQLite, native codecs, browser engines, and operating-system services remain part of the deployed system.
Rust is the payment for the larger boundary
The rewrite targeted cleanup failures rather than missing feature capacity. Jarred Sumner's account describes recurring use-after-free, double-free, and forgotten-cleanup bugs across a large native codebase. The team mechanically ported 535,496 lines of Zig with Claude Code, kept the TypeScript test suite as the behavioral contract, then used Rust ownership and sanitizer builds to expose lifetime problems.
That mechanism is credible; the phrase “Bun is memory-safe now” outruns the evidence. Bun's own post says roughly
four percent of the Rust code uses unsafe, and the runtime still crosses large C and C++ boundaries.
Rust can make Bun-owned resource cleanup more deterministic and move more lifetime errors to compile time. It
cannot make a protocol correct, model every event-loop ordering, or prove an FFI dependency safe.
| Claim | Evidence | Boundary |
|---|---|---|
| The rewrite preserved behavior | Maintainers report six green platforms and the full suite passing | Integration-only states still escape test coverage |
| The rewrite reduced leak exposure | Rust cleanup, ASAN wiring, and fixed 1.3.14-repro bugs | Native libraries and unsafe code remain |
| 1.4 uses less CPU and memory | Published Bun and Claude Code measurements | Maintainer-run workloads, not an independent benchmark here |
The strongest outside evidence comes from a failure, not a benchmark. Prisma Compute had two Bun 1.3 problems: memory growth while reading S3 objects and a SQL connection pool that did not recover after scale-to-zero. Prisma says the Rust canary passed both tests and launched its public beta on that build. It also explicitly refuses the larger conclusion: two repaired failure modes do not prove every pool or memory problem fixed.
Compatibility percentages cannot model an event loop
Bun reports 1,517 more Node.js suite tests and pass rates of at least 97 percent for several core modules. That is
valuable evidence because compatibility is an accumulation of small semantics. Production workloads still add
states the suite does not model. A post-release WebSocket report found that delaying authentication before
handleUpgrade() could leave an upgrade hanging in 1.4.0. The repair merged two days after the release
tag. A broad suite could be green while one ordering between authentication, upgrade, and socket lifecycle was wrong.
The timing matters more than using the bug as a scare story. The issue had a bounded reproduction, maintainers repaired it, and the fix was not in 1.4.0. “Fixed on main” and “safe in the tagged binary” are different states. Pinning a runtime makes that distinction operational instead of rhetorical.
Run the same service under its current Node version and pinned Bun for seven days—or a predeclared production volume. Promote only after response bytes, status codes, durable writes, queue effects, and error classification agree; no Bun-only hangs appear; diagnostics remain complete; and one declared resource gain survives.
The security obligation moved too
I also passed an image containing a raw onerror attribute through Bun.markdown.html() on
the official release binary. The attribute survived, exactly as Bun's documentation warns. That is a parser
contract, not a security boundary. The important point is ownership. Deleting a Markdown package does not delete
the application's obligation to sanitize untrusted content.
The browser story has the same shape. Bun.WebView can remove Puppeteer from a supported automation
path, but macOS uses system WebKit and other platforms use an installed Chrome-family browser. The API is
experimental. A deployment may have fewer npm nodes while depending more heavily on OS versions, browser
provisioning, profile isolation, and Bun's release cadence.
The package graph measures who you install. It does not measure everything you trust.
Anthropic is both the advantage and the bias
Anthropic acquired Bun in December 2025. Claude Code ships as a Bun executable and exercised the Rust port for months before stable release. That gives Bun something most runtimes lack: funding plus a demanding, long-lived, widely distributed owner workload. Terminal control, browser automation, package review, test selection, self-contained executables, and machine-readable performance reports make sense together when AI coding tools are a strategic user.
The same evidence is correlated. Claude Code proves Bun can carry serious work, but it is no longer an independent customer choosing among neutral vendors. Prisma's beta and Vercel's first-party Bun runtime broaden the case, while Vercel's public-beta label and documented Node-relative gaps keep it open. Bun's reported monthly downloads do not reveal how many teams use the package manager, the runtime, a compiled CLI, or production servers.
Fifty agents make small runtime costs expensive
My laptop is an M5 MacBook Pro with 24 GB of RAM. The CPU, memory, and disk are fast, and I still feel the machine slow down. A normal workday can include Chrome, VS Code, ChatGPT's desktop app or Claude Cowork, several CLIs and MCP servers, and many Claude Code or Codex sessions at once. The metric I care about is how much of the workstation remains after all of them are running.
This is why Bun's resource numbers matter more to me now than they would have a few years ago. Saving a little idle CPU or tens of megabytes in one process can look trivial on modern hardware. Keep 10–15 agent processes and their tools alive, then aim for 50–100, and the same steady-state costs become the limit on useful parallel work. These benchmarks measure the runtime cost beneath every Bun-based agent; total agent capacity remains unmeasured.
Bun attributes the CPU drop to better garbage-collector timer requests, a segmented-array layout for visiting
JavaScriptCore Strong roots, fewer futex calls, and allocator changes. Claude Code is owner evidence,
not an independent benchmark, but it is the right shape of workload: a large application that stays alive while
tools, subprocesses, and user sessions keep moving through it.
Memory decides how many workers stay resident
Bun measured peak memory under one million requests with 64 connections, except the Next.js and Vite runs, which used 100,000 requests. Every tested Bun server used less peak memory than it did on 1.3:
| Server | Bun 1.4 | Bun 1.3 | Node.js 26 | Change vs 1.3 |
|---|---|---|---|---|
| Fastify | 120 MB | 233 MB | 156 MB | −48% |
| Express | 92 MB | 169 MB | 145 MB | −46% |
| node:http | 81 MB | 135 MB | 107 MB | −40% |
| Elysia | 55 MB | 91 MB | n/a | −40% |
| Next.js | 285 MB | 397 MB | 342 MB | −28% |
| Bun.serve | 36 MB | 45 MB | n/a | −20% |
| Vite dev server | 233 MB | 268 MB | 214 MB | −13% |
The long-running cases are even more relevant to agent software. In Bun's Next.js App Router test, a dynamic route
using React.cache and a no-store fetch settled at 238 MB after 4,000 pages on Bun 1.4.
Node settled at 410 MB; Bun 1.3 kept growing. A 100-deep Response.clone() chain around a 10 MB stream
fell from about 1,050 MB to 20 MB. Those are narrow tests, but both remove the kind of retained memory that makes a
long session decay while the developer is still using it.
Startup and binary size constrain fan-out
| Hello-world process | Bun 1.4 | Bun 1.3 | Node.js 26 |
|---|---|---|---|
| Windows startup | 15.5 ms | 39.0 ms | 40.1 ms |
| Windows peak memory | 16.8 MB | 46.5 MB | 32.5 MB |
| Linux startup | 5.1 ms | 10.9 ms | 27.2 ms |
| Linux peak memory | 14.6 MB | 33.0 MB | 44.5 MB |
Short-lived workers pay startup cost on every invocation. Resident workers pay the memory cost for their whole lifetime. Bun 1.4 cuts both in these Linux and Windows tests, which makes process-level isolation less expensive for schedulers that start tools on demand instead of keeping every tool inside one host.
| Runtime binary | Bun 1.4 | Bun 1.3.14 | Result |
|---|---|---|---|
| Linux x64 | 77.0 MB | 88.5 MB | 11.5 MB smaller |
| Linux arm64 | 76.8 MB | 87.6 MB | 10.8 MB smaller |
| Windows x64 | 84.8 MB | 93.9 MB | 9.1 MB smaller |
| Windows arm64 | 75.1 MB | 90.2 MB | 15.1 MB smaller |
| macOS arm64 | 61.2 MB | 60.2 MB | 1.0 MB larger |
| macOS x64 | 66.6 MB | 66.0 MB | 0.6 MB larger |
Bun can also compile TypeScript or JavaScript, its imports, and a copy of the runtime into one executable for a supported operating-system and architecture target. That does not turn JavaScript into a tiny native program; each executable still embeds Bun. It does give agent tools one deployable file and one runtime boundary instead of a separate Node installation and package tree.
I want 10–15 parallel agent sessions to feel ordinary, then 50–100 terminal panes without saturating the machine. A runtime cannot erase the cost of models, browsers, editors, MCP servers, or the work each agent performs. Bun 1.4 still moves in the direction I care about: more work per machine, a smaller steady-state cost, and one fast toolchain with which to build the next layer of agent software. To me, these improvements buy capacity.
What engineers get from Bun 1.4 in practice
Bun's original moat was the integrated JavaScript toolchain. Version 1.4 pushes that integration upward into application work. Node compatibility is becoming the entry contract beneath a Bun-specific toolkit, not the final destination. That is a stronger product and a larger bet.
The architecture matters only if it removes work or makes a production failure less likely. The practical list below uses the full 1.3-to-1.4 release train and keeps the final tag boundary visible. These are the changes that make me want to test Bun again on real projects.
The test runner and tracer can follow the application
Bun reports that Playwright, Vitest with coverage and worker pools, OpenTelemetry HTTP and file
instrumentation, dd-trace, and @datadog/pprof now run on 1.4.0. Those are migration
blockers, not demo features. Verify the exact plugins and transports you use. Addons bound directly to
Node, V8, libuv, or the C++ ABI need matching prebuilds for Bun's Node 26.3.0 target; Node-API addons retain
their cross-version ABI contract.
Large test suites get scheduling primitives
--parallel, --shard, --changed, and isolation now compose with
--timings=<path> and --update-timings, so workers and CI runners can start with
slow tests instead of dividing files evenly. Keep a no-output watchdog and a serial fallback: a
large-suite deadlock report
remained open at the cutoff.
Performance evidence becomes plain Markdown
--cpu-prof-md, --heap-prof-md, and --metafile-md emit call
trees, retaining chains, and bundle dependency paths that work over SSH, in an issue, or inside an agent's
context. Most of this arrived during 1.3.x. The 1.4 value is the coherent workflow: measure, inspect, change,
and compare without converting a GUI profile or a large JSON file first.
Agent tools gain native terminal, browser, and image surfaces
A TypeScript process can drive a real PTY with Bun.Terminal, navigate and screenshot
with Bun.WebView, and resize or encode an image with Bun.Image. That covers much of a
coding agent's observe-and-act loop inside one runtime. It does not supply approvals, secret policy, session
recovery, or cross-platform parity; WebView is experimental and still relies on OS or installed browsers.
Package maintenance becomes one reviewable workflow
bun pm diff, bun audit fix --dry-run, bun dedupe,
bun prune --production, and bun pm licenses --prod --json cover upgrade review,
advisory repair, lockfile cleanup, deployment pruning, and license inventory. Audit sends installed package
names and versions to the configured registries; it is not an offline scan. Keep repair changes on a branch:
an open report
shows audit fix --latest selecting an older, more vulnerable major.
React Compiler can become a Bun build option
bun build --react-compiler runs React's auto-memoization transform inside Bun's parser
instead of adding another Babel or SWC parse-and-print pass. Bun reports 71 ms of added work across roughly
860 components versus 9.15 seconds for the Babel plugin on the same input. Treat that as one measured codebase,
then compare your plugins, output, and runtime behavior before removing the old compiler path.
Monorepo fan-out can lose two coordinator packages
bun run --parallel --filter prefixes output by package and script, preserves pre/post
hooks, and can continue after failures. That absorbs common concurrently and
npm-run-all jobs. The related test flags shorten the edit-test loop further, but Bun still does
not provide a distributed task graph, remote cache, or isolation policy for shared ports and databases.
bun --hotno longer loses track of macOS files after an editor's atomic rename.Bun.Globand recursivefs.readdirno longer skip files on bind mounts, FUSE, or NFS.
This is the part that genuinely excites me. Bun says the 1.3-to-1.4 train fixed more than 2,900 issues, while the
Rust port fixed 128 bugs that still reproduced on 1.3.14. The one-time cutover now sits behind a stable tag. In
the first week after release, smaller fixes, ownership cleanup, and performance work were already landing in an
official 1.4.1 canary. That proves fast delivery from main to canary, not a faster stable cadence; at
the August 26 cutoff, 1.4.0 was still the only stable 1.4 release. What changed is that the next fixes no longer
have to share a release milestone with the mechanical port. Whether the 1.4.x line turns that into smaller,
quicker stable releases is the next result I want to see.
Adopt by canary, not by ambient upgrade
Bun 1.4 is a credible candidate for TypeScript CLIs, coding tools, greenfield services, and incremental package, test, or bundling adoption. Record the exact revision, keep rollback cheap, and test the Bun-native API on every operating system you ship.
Gate services with delayed WebSocket authentication, browser automation, Bun-native operating-system APIs, and workloads that depend on exact Node behavior. Wait when the migration needs mature host observability or cross-runtime behavior that your application cannot independently verify.
Bun 1.4 preserved enough behavior through a source-language migration to keep widening the runtime boundary. That combination deserves a serious production trial because the integration is real, the test investment is large, and the canary evidence reaches beyond microbenchmarks. A blind replacement is still unjustified because every deleted package line concentrates more behavior behind one upgrade.
The package graph shrank. The dependency became Bun.
Research footprint
Open the evidence artifacts ↗The planning envelope was 90–120 minutes and 250,000–400,000 aggregate tokens. The measured freeze arrived after 292 minutes, from to . The token estimate was badly wrong because cumulative accounting counts the full context presented on every response, including cache hits.
The 114,317,897 total equals 113,943,245 input plus 374,652 output tokens. Of the input, 110,553,216 came from cache and 3,390,029 did not. Reasoning output (131,130) is a subset of output, not an extra addition. This OSS Radar issue has no public raw-prompt transcript; its prompt count is zero by publication rule.
The frozen manifest records the recursive session closure, selected cumulative epoch ends, log-prefix hashes, artifact counts, and timing. The root counter reset once after compaction, so rules version 2 counts its two monotonic epochs; the other three sessions have one. Cached input is part of input, and reasoning output is part of output.
The counter-reset audit also records four automatic guardian sessions totaling 956,584 tokens. They have no parent-thread link and remain outside the headline total. This number describes the article's linked research-session tree, not every internal token the Codex product may have processed around it.
This is provenance, not a quality score, bill, or environmental estimate. Wall-clock time is not hands-on time. The private session logs are not published; the prefix commitments make the record auditable by the author but do not let a reader independently reconstruct the totals.