Bun

Bun v1.3.12


Jarred Sumner Β· April 9, 2026

To install Bun

curl
npm
powershell
scoop
brew
docker
curl
curl -fsSL https://bun.sh/install | bash
npm
npm install -g bun
powershell
powershell -c "irm bun.sh/install.ps1|iex"
scoop
scoop install bun
brew
brew tap oven-sh/bun
brew install bun
docker
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun

To upgrade Bun

bun upgrade

Bun.WebView β€” Headless Browser Automation

Bun now ships with native headless browser automation built into the runtime. Two backends, one API:

  • WebKit (macOS default) β€” uses the system WKWebView. Zero external dependencies.
  • Chrome (cross-platform) β€” Chrome/Chromium via DevTools Protocol. Auto-detects installed browsers or accepts a custom path.

All input is dispatched as OS-level events β€” sites can't distinguish view.click() from a real mouse click (isTrusted: true). Selector-based methods auto-wait for actionability, Playwright-style: the element must be attached, visible, stable, and unobscured before the action fires.

await using view = new Bun.WebView({ width: 800, height: 600 });
await view.navigate("https://bun.sh");

await view.click("a[href='/docs']"); // waits for actionability, native click
await view.scroll(0, 400); // native wheel event, isTrusted: true
await view.scrollTo("#install"); // scrolls every ancestor, waits for visible

const title = await view.evaluate("document.title");
const png = await view.screenshot({ format: "jpeg", quality: 90 });
await Bun.write("page.jpg", png);

All methods work across both backends:

MethodDescription
navigate(url)Navigate to a URL
evaluate(expr)Evaluate JavaScript in the page
screenshot({format, quality, encoding})Capture a PNG/JPEG/WebP screenshot
click(x, y) / click(selector)Click at coordinates or a CSS selector
type(text)Type text into the focused element
press(key, {modifiers})Press a key with optional modifiers
scroll(dx, dy) / scrollTo(selector)Scroll by delta or to an element
goBack() / goForward() / reload()Navigation controls
resize(w, h)Resize the viewport
cdp(method, params)Raw Chrome DevTools Protocol call
view.url / view.title / view.loadingPage state properties

Bun.WebView extends EventTarget β€” on the Chrome backend, CDP events are dispatched as MessageEvents with the params on event.data. Constructor options include backend ("webkit", "chrome", or { type: "chrome", path, argv }), console to capture page logs, and dataStore for persistent profiles. One browser subprocess is shared per Bun process; additional new Bun.WebView() calls open tabs in the same instance.

Render Markdown in the Terminal with bun ./file.md

You can now render Markdown files directly in your terminal. When you run bun ./file.md, Bun reads the file, renders it as beautifully formatted ANSI output, and prints it to stdout with no JavaScript VM startup overhead.

You can also use the new Bun.markdown.ansi() API programmatically:

// Render markdown to an ANSI-colored string
const out = Bun.markdown.ansi("# Hello\n\n**bold** and *italic*\n");
process.stdout.write(out);

// Plain text mode β€” no escape codes
const plain = Bun.markdown.ansi("# Hello", { colors: false });

// Enable clickable hyperlinks
const linked = Bun.markdown.ansi("[docs](https://bun.sh)", {
  hyperlinks: true,
});

// Custom line width for wrapping
const wrapped = Bun.markdown.ansi(longText, { columns: 60 });

// Inline images via Kitty Graphics Protocol (Kitty, WezTerm, Ghostty)
const withImg = Bun.markdown.ansi("![alt](./logo.png)", {
  kittyGraphics: true,
});

Async stack traces for native errors

In-process Bun.cron() scheduler

Bun.cron now supports an in-process callback overload that runs a function on a cron schedule. This is ideal for long-running servers and containers where you want scheduled work that shares state with the rest of your application.

This complements the existing OS-level Bun.cron(path, schedule, title) which registers persistent crontab/launchd/Task Scheduler entries. The in-process variant is lighter, works identically across platforms, and lets your handler access database pools, caches, and module-level state directly.

Key behaviors:

  • No overlap β€” the next fire is scheduled only after the handler (and any returned Promise) settles. Slow async work won't pile up concurrent runs.
  • Scheduled in UTC β€” 0 9 * * * means 9:00 UTC, regardless of the system time zone. (The OS-level Bun.cron(path, schedule, title) variant uses system local time, since that's how crontab/launchd/Task Scheduler work.)
  • Error handling matches setTimeout β€” synchronous throws emit uncaughtException, rejected promises emit unhandledRejection. Without a listener the process exits with code 1; with one, the job reschedules itself.
  • --hot safe β€” all in-process cron jobs are cleared before the module graph re-evaluates, so editing the schedule, handler, or removing the call entirely all take effect on save without leaking timers.
  • Disposable β€” using job = Bun.cron(...) auto-stops at scope exit.
  • ref/unref β€” .ref() (default) keeps the process alive; .unref() lets it exit naturally.
// Error handling example
process.on("unhandledRejection", (err) => console.error("cron failed:", err));

Bun.cron("* * * * *", async () => {
  await mightThrow(); // logged, then retried next minute
});

Thanks to @alii for the contribution!

UDP Socket: ICMP Error Handling and Truncation Detection

Two improvements to Bun.udpSocket() that bring it closer to libuv/Node.js behavior:

ICMP errors no longer silently close the socket. On Linux, sending a UDP packet to an unreachable port previously caused the socket to silently close, breaking all other sends on the same socket. Now, ICMP errors (port unreachable, host unreachable, TTL exceeded, etc.) are surfaced through the error handler, and the socket stays open:

const sock = await Bun.udpSocket({
  socket: {
    error(err) {
      console.log(err.code); // 'ECONNREFUSED'
    },
  },
});
sock.send("ping", 1, "127.0.0.1"); // dead port β€” error handler fires, socket stays open

Truncated datagrams are now detectable. When a received datagram is larger than the receive buffer, the kernel silently truncates it. The data callback now receives a fifth flags argument so you can tell truncated payloads from complete ones:

const sock = await Bun.udpSocket({
  socket: {
    data(socket, data, port, address, flags) {
      if (flags.truncated) {
        console.log("Datagram was truncated!");
      }
    },
  },
});

Unix Domain Socket Lifecycle Now Matches Node.js

Bun's unix domain socket behavior was inverted from Node.js/libuv in two important ways:

Node.js / libuvBun (before)
Existing file at bind timeEADDRINUSESilently unlinks and binds anyway
Socket file after close()RemovedLeft on disk

This meant Bun could silently steal a live socket from another process on listen(), and leaked .sock files on stop()/close().

Now Bun matches Node.js semantics: binding to an existing socket file correctly returns EADDRINUSE, and closing a listener automatically cleans up the socket file. This applies to Bun.listen, Bun.serve, and net.Server with unix sockets.

import { existsSync } from "node:fs";

const listener = Bun.listen({
  unix: "/tmp/my.sock",
  socket: { data() {}, open() {} },
});

existsSync("/tmp/my.sock"); // true

listener.stop();

existsSync("/tmp/my.sock"); // false β€” automatically cleaned up
// Binding to an existing socket now correctly throws EADDRINUSE
// instead of silently taking over the socket
import { listen } from "bun";

const a = listen({ unix: "/tmp/my.sock", socket: { data() {}, open() {} } });

try {
  // Previously: silently unlinked and stole the socket
  // Now: throws EADDRINUSE, matching Node.js behavior
  const b = listen({ unix: "/tmp/my.sock", socket: { data() {}, open() {} } });
} catch (e) {
  console.log(e.code); // "EADDRINUSE"
} finally {
  a.stop();
}

Upgraded JavaScriptCore Engine

Bun's underlying JavaScript engine (WebKit's JavaScriptCore) has been upgraded with over 1,650 upstream commits, bringing significant performance improvements, new language features, and bug fixes.

Explicit Resource Management (using and await using)

The using and await using declarations from the TC39 Explicit Resource Management proposal are now supported natively in JavaScriptCore:

function readFile(path) {
  using file = openFile(path); // file[Symbol.dispose]() called automatically at end of block
  return file.read();
}

async function fetchData(url) {
  await using connection = await connect(url); // connection[Symbol.asyncDispose]() awaited at end of block
  return connection.getData();
}

JIT Compiler Improvements

  • Quick tier-up β€” Functions that are proven stable now tier up to optimized DFG/FTL compilation faster, improving steady-state performance.
  • Array.isArray intrinsic β€” Array.isArray() is now a JIT intrinsic, making it significantly faster in hot paths.
  • Faster String#includes β€” Uses an optimized single-character search fast path.
  • Improved BigInt performance β€” Smaller memory footprint and faster arithmetic operations.
  • Better register allocation β€” Rewritten greedy register allocator coalescing for improved generated code.
  • Faster promise resolution β€” Micro-optimized promise reaction triggering and microtask queue draining, with a new PerformPromiseThen DFG/FTL optimization node.

WebAssembly Improvements

  • SIMD shuffle optimizations and additional ARM64/x64 SIMD instruction support.
  • Memory64 bounds checking fixes.
  • Improved BBQ and OMG compiler codegen (conditional selects, better write barriers, tail-call fixes).

JavaScript Spec Conformance Fixes

  • TypedArray#sort when the comparator accesses .buffer
  • Array#includes(undefined, fromIndex) hole handling
  • Array#flat with depth 0 and derived array bailout
  • Array.prototype.concat now checks all indexed accessors
  • Array.prototype.copyWithin return value
  • Set methods properly throw on non-object iterator next() result
  • RegExp#@@matchAll lastIndex clamping
  • String#replace surrogate-advancement fix for non-unicode regexps
  • Named vs numbered backreference handling in RegExp
  • Private fields no longer have attributes set when sealing/freezing objects

Memory Allocator (libpas) Improvements

  • Retag-on-scavenge for improved memory safety
  • Page-based zeroing threshold reduced from 64MB to 1MB for faster large allocations
  • Faster bitfit heap utilization

Thanks to @sosukesuzuki for the contribution!

Improved standalone executables on Linux

Standalone executables created with bun build --compile on Linux now use a proper ELF section (.bun) to embed the module graph, matching the existing approach on macOS and Windows. Previously, the embedded data was read from /proc/self/exe at startup, which failed when the binary had execute-only permissions (chmod 111).

With this change, the kernel maps the data via PT_LOAD during execve β€” meaning zero file I/O at startup and no read permission required on the binary.

bun build --compile app.ts --outfile myapp
chmod 111 myapp
./myapp  # works now on Linux

Thanks to @dylan-conway for the contribution!

URLPattern is up to 2.3x faster

URLPattern.test() and URLPattern.exec() are now significantly faster. The internal regex matching now calls the compiled regex engine directly instead of allocating temporary JavaScript objects for each URL component, eliminating up to 24 GC allocations per call.

BenchmarkBeforeAfterSpeedup
test() match - named groups1.05 Β΅s487 ns2.16x
test() no-match579 ns337 ns1.72x
test() match - simple971 ns426 ns2.28x
test() match - string pattern946 ns434 ns2.18x
exec() match - named groups1.97 Β΅s1.38 Β΅s1.43x
exec() no-match583 ns336 ns1.73x
exec() match - simple1.89 Β΅s1.30 Β΅s1.45x
const pattern = new URLPattern({ pathname: "/api/users/:id/posts/:postId" });

// 2.16x faster
pattern.test("https://example.com/api/users/42/posts/123");

// 1.43x faster
pattern.exec("https://example.com/api/users/42/posts/123");

As a side effect, URLPattern internals no longer pollute RegExp.lastMatch / RegExp.$N β€” previously, calling pattern.test(url) would leak internal regex state into these legacy static properties.

Thanks to @sosukesuzuki for the contribution!

Faster Bun.stripANSI and Bun.stringWidth

SIMD optimizations across Bun.stripANSI, Bun.stringWidth, and the shared ANSI parsing helpers used by Bun.sliceAnsi, Bun.wrapAnsi, and Node's readline.getStringWidth.

Key improvements:

  • 4Γ—-unrolled SIMD prologue for escape character scanning β€” processes 64 bytes at a time instead of 16, reducing the cost of the NEONβ†’GPR transfer that gates the loop branch.
  • SIMD terminator scans inside ANSI escape parsing β€” CSI and OSC payloads (like hyperlink URLs) are now skipped in bulk instead of byte-by-byte.
  • Lazy flat buffer allocation in stripANSI β€” replaces StringBuilder with a raw Vector<Char> + memcpy, eliminating per-append bookkeeping and the final shrink-copy.
  • UTF-16 stringWidth escape state machine refactor β€” long OSC payloads in UTF-16 strings (hyperlinks with emoji) now use bulk SIMD scans instead of per-codepoint stepping.
  • C1 ST (0x9C) recognized as OSC terminator in the Zig stringWidth path, conforming to ECMA-48 and matching the C++ consumeANSI behavior.

stripANSI benchmarks

InputBeforeAfterImprovement
Plain ASCII (1000 chars, no escapes)65.40 ns16.88 ns~4Γ—
OSC 8 hyperlink (45 chars)59.57 ns45.12 ns24% faster
Bash 150KB78.97 Β΅s71.56 Β΅s9% faster

stringWidth benchmarks

InputBeforeAfterImprovement
Hyperlink + emoji, UTF-16 (440K chars)~2.0 ms180 Β΅s~11Γ—
Truecolor SGR (140K chars)~135 Β΅s120 Β΅s10% faster
Hyperlink, Latin-1 (445K chars)~135 Β΅s119 Β΅s11% faster

Compared to npm string-width, Bun is 4–822Γ— faster depending on input size and content, and correctly handles all three OSC terminator variants (BEL, ESC \, and C1 ST) where the npm package only recognizes BEL.

Faster bun build on low-core machines

Fixed a thread-pool bug that left the bundler running with one fewer worker thread than intended. Most impactful on low-core machines where one thread is a larger share of the pool:

CoresBeforeAfterSpeedup
2554–561 ms375–392 ms1.43–1.47Γ—
4321 ms301 ms~1.07Γ—
16303–316 ms292–296 ms~1.02–1.08Γ—

Benchmark: bun build on an 11,669-module project (three.js + @mui/material + @mui/icons-material, 10.45 MB output).

Faster Bun.Glob.scan()

Bun.Glob.scan() no longer opens and reads the same directory twice for patterns with a **/X/... boundary (e.g. **/node_modules/**/*.js). Gains scale with how much of the tree sits under the boundary β€” up to 2x on deeply nested trees. Patterns without a boundary (e.g. **/*.ts) are unchanged.

const glob = new Bun.Glob("**/node_modules/**/*.js");

// This is now up to 2x faster
for await (const path of glob.scan({ cwd: "./my-project" })) {
  // ...
}

Additionally, Bun.Glob on Windows now pushes wildcard filters down to the kernel via NtQueryDirectoryFile, so non-matching entries are discarded before reaching userspace β€” up to 2.4Γ— faster for simple patterns like *.js or pkg-*/lib/*.js in directories with a low match ratio. Patterns using **, ?, [...], or {...} bypass the filter and behave as before.

Cgroup-aware availableParallelism / hardwareConcurrency on Linux

Keep-Alive for HTTPS Proxy CONNECT Tunnels

Bun now reuses CONNECT tunnels for HTTPS-through-proxy requests. Previously, every proxied HTTPS request performed a fresh CONNECT handshake and TLS negotiation. Now, the tunnel and inner TLS session are pooled and reused across sequential requests to the same target through the same proxy with the same credentials β€” matching the behavior of Node.js + undici.

This dramatically reduces latency and connection overhead when making multiple HTTPS requests through a proxy:

// All three requests now reuse a single CONNECT tunnel
// instead of establishing 3 separate tunnels + TLS handshakes
for (let i = 0; i < 3; i++) {
  const res = await fetch("https://example.com/api", {
    proxy: "http://user:pass@proxy.example.com:8080",
  });
  console.log(res.status);
}

Tunnels are keyed by proxy host/port, proxy credentials, target host/port, and TLS configuration β€” so different targets or different credentials correctly use separate tunnels.

This also fixes intermittent Malformed_HTTP_Response errors that some users encountered when using fetch with HTTPS proxies.

Thanks to @cirospaciari for the contribution!

TCP_DEFER_ACCEPT for Bun.serve() on Linux

Bun.serve() now sets TCP_DEFER_ACCEPT on Linux (and SO_ACCEPTFILTER "dataready" on FreeBSD), the same optimization nginx uses to reduce latency for incoming HTTP connections.

Previously, accepting a new connection required two event loop wake-ups β€” one for the accept and another to discover the socket was readable. With TCP_DEFER_ACCEPT, the kernel defers the accept until the client has actually sent data (the HTTP request or TLS ClientHello), collapsing the two wake-ups into one:

Before:

  1. epoll wake β†’ accept new socket
  2. Return to epoll
  3. epoll wake β†’ socket readable β†’ recv() β†’ process β†’ respond

After:

  1. epoll wake β†’ accept new socket (data already buffered) β†’ recv() β†’ process β†’ respond

This is especially impactful for short-lived connections (e.g. HTTP/1.1 with Connection: close). Bun.listen() and net.createServer() are unchanged, since they may serve protocols where the server sends first. No effect on macOS or Windows.

Bugfixes

Node.js compatibility improvements

  • Fixed: process.env being completely empty when the current working directory is inside a directory without read permission (e.g., chmod 111). Previously, OS-inherited environment variables passed via execve were lost because the env loader returned early on EACCES before reading process environment variables. (@alii)
  • Fixed: Memory leak where every vm.Script, vm.SourceTextModule, and vm.compileFunction call leaked the resulting object due to a reference cycle in the internal NodeVMScriptFetcher (@sosukesuzuki)
  • Fixed: pipeline(Readable.fromWeb(res.body), createWriteStream(...)) permanently stalling (and eventually spinning at 100% CPU) when piping fetch() response bodies under concurrency, caused by a race between the HTTP thread's body callback and JS accessing res.body (@dylan-conway)
  • Fixed: Readable.prototype.pipe crashing the process when piping an object-mode Readable into a byte-mode Transform/Writable. The ERR_INVALID_ARG_TYPE error is now properly emitted on the destination stream's error event instead of being thrown as an uncatchable exception, matching Node.js behavior.
  • Fixed: node:dns/promises.getDefaultResultOrder being undefined and dns.getDefaultResultOrder() returning the function object instead of a string ("ipv4first" / "ipv6first" / "verbatim"). Also added the missing getServers export to dns.promises. This broke Vite 8 builds under Bun. (@dylan-conway)
  • Fixed: fs.realpathSync("/") throwing ENOENT when running Bun under FreeBSD's Linuxulator compatibility layer or in minimal containers without /proc mounted (@ant-kurt)
  • Fixed: fs.statSync().ino returning INT64_MAX (9223372036854775807) for files with inodes β‰₯ 2⁢³, causing all files on NFS mounts with high 64-bit inodes to report the same inode number. dev and rdev were also affected. All stat fields now match Node.js behavior for both Stats (Number) and BigIntStats (BigInt) paths. (@dylan-conway)
  • Fixed: process.stdout.end(callback) firing the callback before all data was flushed, causing output truncation at power-of-2 boundaries (64KB, 128KB, etc.) when the callback called process.exit(0)
  • Fixed: Error.captureStackTrace now includes async stack frames (e.g. at async <fn>) matching the behavior of new Error().stack (@Jarred-Sumner)
  • Fixed: a rare crash in Error.captureStackTrace on error objects whose .stack had already been accessed (@Jarred-Sumner)
  • Fixed: assert.partialDeepStrictEqual crashing when comparing arrays
  • Fixed: fs.stat, fs.lstat, and fs.fstat throwing EPERM on Linux when running under seccomp filters that block the statx syscall (e.g., older Docker versions < 18.04, libseccomp < 2.3.3, and various CI sandboxes). Bun now matches libuv's fallback behavior by also handling EPERM, EINVAL, and abnormal positive return codes from statx.
  • Fixed: fs.Stats(...) called without new scrambled property values β€” 8 of 10 integer fields (e.g. ino, size, mode) were assigned to the wrong property names due to a slot-order mismatch in the internal constructor path. (@dylan-conway)
  • Fixed: statSync(path) instanceof Stats incorrectly returned false because stat instances used a different prototype object than Stats.prototype. Methods like .isFile() still worked, but identity checks and instanceof did not match Node.js behavior. (@dylan-conway)
  • Updated built-in root TLS certificates to NSS 3.121, the version shipping in Firefox 149. Adds e-Szigno TLS Root CA 2023 and corrects the label for OISTE Server Root RSA G1. (@cirospaciari)

Bun APIs

  • Fixed: setting process.env.HTTP_PROXY, HTTPS_PROXY, or NO_PROXY (and lowercase variants) at runtime had no effect on subsequent fetch() calls because proxy config was only read once at startup. Changes now take effect on the next fetch(). (@cirospaciari)
  • Fixed: the event loop processing at most 1,024 ready I/O events per tick when more were pending, adding latency on servers handling thousands of concurrent connections. Bun now drains the full backlog in a single tick, matching libuv's uv__io_poll. (@Jarred-Sumner)
  • Fixed: a Bun.serve() performance cliff where concurrent async handlers that resumed after an await couldn't batch their writes (cork buffer contention), dropping throughput from ~190k req/s to ~22k req/s. Also fixes a potential use-after-free where closed sockets could remain referenced in the drain loop. (@Jarred-Sumner)
  • Fixed: a lost-wakeup race in Bun's internal thread pool that could cause fs.promises, Bun.file().text(), Bun.write(), crypto.subtle, and the package manager to hang indefinitely on aarch64 (Apple Silicon, ARM Linux). x86_64 was not affected. (@dylan-conway)
  • Fixed: Memory leak in Bun.serve() when a Promise<Response> from the fetch handler never settles after the client disconnects (@Jarred-Sumner)
  • Fixed: Bun.SQL MySQL adapter returning empty results for SELECT queries against MySQL-compatible databases (StarRocks, TiDB, SingleStore, etc.) that don't support the CLIENT_DEPRECATE_EOF capability. Bun now properly negotiates capabilities with the server per the MySQL protocol spec and correctly handles legacy EOF packets.
  • Fixed: per-query memory leaks in the bun:sql MySQL adapter that caused RSS to grow unboundedly until OOM on Linux. Three native allocation leaks were fixed: column name allocations not freed on cleanup or when overwritten during prepared statement reuse, and parameter slice allocations not freed after query execution.
  • Fixed: memory leak in Bun.TOML.parse where the logger's internal message list was not freed on error paths
  • Fixed: Bun.listen() and Bun.connect() could crash with certain invalid hostname or unix values. Now throws a TypeError instead.
  • Fixed: a crash accessing server.url with an invalid unix socket path
  • Fixed: DNS cache entries that were stale but still referenced by in-flight connections would never expire, causing stale DNS results to persist indefinitely (@dylan-conway)
  • Fixed: potential crash in Bun.dns.setServers with certain invalid inputs
  • Fixed: Bun.dns.lookup() could crash with certain invalid inputs
  • Fixed: Glob scanner crashing or looping infinitely when scanning deeply nested directory trees or self-referential symlinks where the accumulated path exceeds the OS path length limit. Now properly returns an ENAMETOOLONG error instead. (@dylan-conway)
  • Fixed: Unix socket paths longer than 104 bytes (the sun_path limit) now work correctly on macOS. Previously, Bun.serve({ unix }) and fetch({ unix }) would fail with ENAMETOOLONG when the socket path exceeded this limit. (@Jarred-Sumner)
  • Fixed: a crash when reading .fd on a TLS listener created with Bun.listen({ tls })
  • Fixed: crashes in Bun.FFI.linkSymbols() and Bun.FFI.viewSource with invalid symbol descriptors β€” now throw a TypeError instead
  • Fixed: edge case crash when passing an out-of-range value as a file descriptor to APIs like S3Client.write

Web APIs

  • Fixed: unbounded memory growth from messages sent to a closed MessagePort being queued indefinitely and never delivered. 5000 Γ— 64KB postMessage calls to a closed port dropped RSS from 332MB to 1.5MB. (@sosukesuzuki)
  • Fixed: AbortController.signal.reason silently becoming undefined after garbage collection when only the controller was retained. (@sosukesuzuki)
  • Fixed: use-after-free race in BroadcastChannel when a worker-owned channel was destroyed while another thread looked it up. (@sosukesuzuki)
  • Fixed: CookieMap.toJSON() could crash with numeric cookie names
  • Fixed: String.raw corrupting null bytes (U+0000) in tagged template literals, emitting the 6-character string \uFFFD instead of preserving the original byte. This affected libraries like wasm-audio-decoders that embed WASM binaries as yEncoded strings in template literals.
  • Fixed: AbortSignal memory leak when ReadableStream.prototype.pipeTo is called with a signal option and the pipe never completes. A reference cycle between AbortSignal and its abort algorithm callbacks prevented garbage collection even after all user-side references were dropped. (@sosukesuzuki)
  • Fixed: crash when calling bytes() or arrayBuffer() on a Response whose body was created from an async iterable (Symbol.asyncIterator)
  • Fixed: crash when calling ReadableStream.blob() after the Response body was already consumed, now properly rejects with ERR_BODY_ALREADY_USED
  • Fixed: a crash in Request.formData() / Response.formData() / Blob.formData() when the Content-Type header contained a malformed boundary value (@dylan-conway)
  • Fixed: HTTP server now correctly rejects requests with conflicting duplicate Content-Length headers per RFC 9112, preventing potential request smuggling attacks (@dylan-conway)
  • Fixed: WebSocket connections crashing when headers, URLs, or proxy config contained non-ASCII characters. The upgrade request now correctly decodes all inputs as UTF-8. (@Jarred-Sumner)
  • Fixed: edge case crash formatting error messages when Symbol.toPrimitive throws
  • Fixed: a crash that could occur when a stack overflow happened during error message formatting

JavaScript bundler

  • Fixed: bun build --compile on NixOS and Guix producing executables that only ran on the exact same Nix generation, because the compiled binary inherited a /nix/store/... ELF interpreter path. PT_INTERP is now normalized back to the standard FHS path so compiled binaries are portable across Linux systems. (@Jarred-Sumner)
  • Fixed a crash in bun build --compile when CSS files are passed as entry points alongside JS/TS entry points

bun test

  • Fixed: mock.module() could crash when the first argument is not a string
  • Fixed: a crash that could occur when mock.module() triggered auto-install during module resolution
  • Fixed: potential crash in expect.extend with certain invalid inputs
  • Fixed: --elide-lines flag no longer exits with an error in non-terminal environments (e.g., CI, Git hooks). The flag is now silently ignored when stdout is not a TTY, allowing the same command to work in both interactive and non-interactive contexts. (@alii)

Bun Shell

  • Fixed: Bun.$.braces() could crash when called with an empty string

Windows

  • Fixed: tar archive extraction on Windows could write files outside the extraction directory when an entry contained an absolute path (e.g. C:\... or UNC paths) β€” these entries are now skipped (@dylan-conway)

Thanks to 8 contributors!