---
title: Bun v1.3.12
description: "Fixes 120 issues (addressing 219 👍). Render Markdown in the terminal with bun ./file.md, Bun.WebView headless browser automation, in-process Bun.cron() scheduler, async stack traces for native errors, 2.3x faster URLPattern, 2x faster Bun.Glob.scan, cgroup-aware parallelism on Linux, and many bugfixes and Node.js compatibility improvements."
date: "2026-04-09T00:00:00.000Z"
author: jarred
---

#### To install Bun

{% codetabs %}

```sh#curl
$ curl -fsSL https://bun.sh/install | bash
```

```sh#npm
$ npm install -g bun
```

```sh#powershell
$ powershell -c "irm bun.sh/install.ps1|iex"
```

```sh#scoop
$ scoop install bun
```

```sh#brew
$ brew tap oven-sh/bun
$ brew install bun
```

```sh#docker
$ docker pull oven/bun
$ docker run --rm --init --ulimit memlock=-1:-1 oven/bun
```

{% /codetabs %}

#### To upgrade Bun

```sh
$ bun upgrade
```

## `Bun.WebView` — Headless Browser Automation

<!-- https://github.com/oven-sh/bun/commit/010dfa159f7eb8c8fe13bf7673815214f11d4955 -->

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.

{% raw %}

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">In the next version of Bun<br><br>`Bun.WebView` programmatically controls a headless web browser in Bun <a href="https://t.co/Yp8UiNoeoy">pic.twitter.com/Yp8UiNoeoy</a></p>&mdash; Jarred Sumner (@jarredsumner) <a href="https://twitter.com/jarredsumner/status/2034165778103312487?ref_src=twsrc%5Etfw">March 18, 2026</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

{% /raw %}

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.

```ts
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:

| Method                                     | Description                            |
| ------------------------------------------ | -------------------------------------- |
| `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.loading` | Page state properties                  |

<!-- https://github.com/oven-sh/bun/commit/0bcb4025d3e3993dd0a77951decbf0b8f934d4f7 -->

`Bun.WebView` extends `EventTarget` — on the Chrome backend, CDP events are dispatched as `MessageEvent`s 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`

<!-- https://github.com/oven-sh/bun/commit/fa6f69fb31d89a1221eb9735c84615e981da6ee9 -->

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.

{% raw %}

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">In the next version of Bun<br><br>`bun ./hello.md` &amp; `Bun.markdown.ansi(string)` pretty-prints markdown to terminal-friendly ansi text <a href="https://t.co/yp7jYKZL8k">pic.twitter.com/yp7jYKZL8k</a></p>&mdash; Bun (@bunjavascript) <a href="https://twitter.com/bunjavascript/status/2040257344404410431?ref_src=twsrc%5Etfw">April 4, 2026</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

{% /raw %}

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

```ts
// 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

<!-- https://github.com/oven-sh/bun/commit/9804f55e762ada50e595e693361e3ed770d7796a -->

{% raw %}

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">In the next version of Bun<br><br>Async stacktraces are supported on native APIs like node:fs, Bun.write, node:http, node:dns &amp; more. <br><br>This makes debugging easier <a href="https://t.co/PHospWtxtg">pic.twitter.com/PHospWtxtg</a></p>&mdash; Jarred Sumner (@jarredsumner) <a href="https://twitter.com/jarredsumner/status/2038445693321482434?ref_src=twsrc%5Etfw">March 30, 2026</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

{% /raw %}

## In-process `Bun.cron()` scheduler

<!-- https://github.com/oven-sh/bun/commit/cf11b7d754069f6d97ca5c2293491ec1d028505b -->

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

{% raw %}

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">In the next version of Bun<br><br>`Bun.cron()` accepts a callback for recurring in-process tasks <a href="https://t.co/HQ3s3rxGEO">pic.twitter.com/HQ3s3rxGEO</a></p>&mdash; alistair (@alistaiir) <a href="https://twitter.com/alistaiir/status/2039476378916257848?ref_src=twsrc%5Etfw">April 1, 2026</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

{% /raw %}

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.

```ts
// 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

<!-- https://github.com/oven-sh/bun/commit/9850dd5b3d24e919fba006bfdb281d689ebcc275 -->

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:

```js
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:

```ts
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

<!-- https://github.com/oven-sh/bun/commit/3f41407f47eb009c654e45def5f3f67d6ce6c8ee -->

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

|                             | Node.js / libuv | Bun (before)                      |
| --------------------------- | --------------- | --------------------------------- |
| Existing file at bind time  | `EADDRINUSE`    | Silently unlinks and binds anyway |
| Socket file after `close()` | Removed         | Left 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.

```js
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
```

```js
// 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

<!-- https://github.com/oven-sh/bun/commit/73361607d70bd77041d0fc20e45a6dbe2373a677 -->

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](https://github.com/tc39/proposal-explicit-resource-management) are now supported natively in JavaScriptCore:

```js
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

<!-- https://github.com/oven-sh/bun/commit/66f7c41412e8a41c9686b0f4524b778a5f69b40e -->

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.

```bash
$ 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

<!-- https://github.com/oven-sh/bun/commit/fe4a66e086bebd2c3c5a238effa801426d736278 -->

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

| Benchmark                       | Before  | After   | Speedup   |
| ------------------------------- | ------- | ------- | --------- |
| `test()` match - named groups   | 1.05 µs | 487 ns  | **2.16x** |
| `test()` no-match               | 579 ns  | 337 ns  | **1.72x** |
| `test()` match - simple         | 971 ns  | 426 ns  | **2.28x** |
| `test()` match - string pattern | 946 ns  | 434 ns  | **2.18x** |
| `exec()` match - named groups   | 1.97 µs | 1.38 µs | **1.43x** |
| `exec()` no-match               | 583 ns  | 336 ns  | **1.73x** |
| `exec()` match - simple         | 1.89 µs | 1.30 µs | **1.45x** |

```js
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`

<!-- https://github.com/oven-sh/bun/commit/4760d78b325b62ee62d6e47b7e8b29e58099bf4a -->

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

| Input                                | Before   | After    | Improvement    |
| ------------------------------------ | -------- | -------- | -------------- |
| Plain ASCII (1000 chars, no escapes) | 65.40 ns | 16.88 ns | **~4×**        |
| OSC 8 hyperlink (45 chars)           | 59.57 ns | 45.12 ns | **24% faster** |
| Bash 150KB                           | 78.97 µs | 71.56 µs | **9% faster**  |

### `stringWidth` benchmarks

| Input                                  | Before  | After  | Improvement    |
| -------------------------------------- | ------- | ------ | -------------- |
| Hyperlink + emoji, UTF-16 (440K chars) | ~2.0 ms | 180 µs | **~11×**       |
| Truecolor SGR (140K chars)             | ~135 µs | 120 µs | **10% faster** |
| Hyperlink, Latin-1 (445K chars)        | ~135 µs | 119 µs | **11% 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

<!-- https://github.com/oven-sh/bun/commit/10a573cf2ca9eb1fca161b53390db27d596c3796 -->

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:

| Cores | Before     | After      | Speedup        |
| ----- | ---------- | ---------- | -------------- |
| 2     | 554–561 ms | 375–392 ms | **1.43–1.47×** |
| 4     | 321 ms     | 301 ms     | ~1.07×         |
| 16    | 303–316 ms | 292–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()`

<!-- https://github.com/oven-sh/bun/commit/e59a147d615a0b95d446c75ba836717cf0dbc513 -->
<!-- https://github.com/oven-sh/bun/commit/5b7fe81279a40f3fccebe6e7f52278c81b39dfb6 -->

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

```js
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

<!-- https://github.com/oven-sh/bun/commit/85f073fdcd5a0b227e3354fed15f0c0d781dfd4c -->

{% raw %}

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">In the next version of Bun <br><br>Threadpool &amp; JIT threads now respect cgroup CPU limits instead of physical cores. This improves resource utilization in Docker &amp; k8s<a href="https://t.co/HCPy7jRCyG">https://t.co/HCPy7jRCyG</a></p>&mdash; Jarred Sumner (@jarredsumner) <a href="https://twitter.com/jarredsumner/status/2039984578778263583?ref_src=twsrc%5Etfw">April 3, 2026</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

{% /raw %}

## Keep-Alive for HTTPS Proxy CONNECT Tunnels

<!-- https://github.com/oven-sh/bun/commit/687700d842737a07f3cf530cf4cabc0015685631 -->

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:

```js
// 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

<!-- https://github.com/oven-sh/bun/commit/1cc837687b1d1f8d558a40110fbe3e61cc41fbcd -->

`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!

- [@alii](https://github.com/alii)
- [@ant-kurt](https://github.com/ant-kurt)
- [@cirospaciari](https://github.com/cirospaciari)
- [@dylan-conway](https://github.com/dylan-conway)
- [@gameroman](https://github.com/gameroman)
- [@jarred-sumner](https://github.com/jarred-sumner)
- [@robobun](https://github.com/robobun)
- [@sosukesuzuki](https://github.com/sosukesuzuki)
