cd /news/developer-tools/bun-v1-3-14 Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-9944] src=bun.com β†— pub= topic=developer-tools verified=true sentiment=↑ positive

Bun v1.3.14

Here is a factual summary of the article: Bun version 1.3.14 introduces a built-in `Bun.Image` API for server-side image processing, supporting formats like JPEG, PNG, WebP, and GIF with chainable transforms such as resize, rotate, and format conversion, designed as a zero-dependency alternative to sharp. The update also adds an experimental global virtual store for `bun install --linker=isolated`, which uses symlinks instead of file copies to significantly speed up warm installs by sharing packages from a central cache.

read56 min views20 publishedMay 13, 2026

To install Bun

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

To upgrade Bun

bun upgrade

Bun.Image

β€” Built-in Image Processing

Bun.Image

β€” Built-in Image ProcessingBun now ships a built-in image processing API that handles JPEG, PNG, WebP, GIF, and BMP β€” plus HEIC, AVIF, and TIFF on macOS and Windows β€” with zero native module installs.

Bun.Image

provides a chainable pipeline for decoding, transforming, and encoding images, designed as a drop-in alternative to sharp for common server-side image operations.

// Resize and convert a photo to WebP
await Bun.file("photo.jpg")
  .image()
  .resize(1024, 1024, { fit: "inside" })
  .rotate(90)
  .webp({ quality: 85 })
  .write("thumb.webp");

// Generate a thumbnail from an upload in a single expression
return new Response(new Bun.Image(upload).resize(200).jpeg());

Input sources

Bun.Image

accepts path strings, ArrayBuffer

/TypedArray

(zero-copy), Blob

/BunFile

/S3File

, and data:

URLs. You can also use Bun.file("photo.jpg").image()

or blob.image()

to start a pipeline.

Chainable transforms

The pipeline supports .resize(w, h?, {filter, fit, withoutEnlargement})

, .rotate(90|180|270)

, .flip()

, .flop()

, and .modulate({brightness, saturation})

. Output format is set with .jpeg()

, .png()

, .webp()

, .heic()

, or .avif()

β€” each with format-specific quality/compression options.

Resize filters

All sharp filters are supported: nearest

, box

, bilinear

, cubic

, mitchell

, lanczos2

, lanczos3

, plus mks2013

and mks2021

.

Terminal methods

All processing runs off the main thread (except metadata()

). Output via .bytes()

, .buffer()

, .blob()

, .toBase64()

, .dataurl()

, .placeholder()

(thumbhash), .metadata()

, or .write(dest)

.

const meta = await new Bun.Image(buf).metadata();
// { width: 1920, height: 1080, format: "jpeg", ... }

const placeholder = await Bun.file("hero.jpg").image().placeholder(); // thumbhash data URL for blur-up

Body integration

Bun.Image

instances work directly as response/request bodies with automatic Content-Type

:

return new Response(new Bun.Image(upload).resize(200).jpeg());

Platform-specific formats

Format macOS Windows Linux
JPEG βœ… βœ… βœ…
PNG βœ… βœ… βœ…
WebP βœ… βœ… βœ…
GIF βœ… βœ… βœ…
BMP (simple) βœ… βœ… βœ…
TIFF βœ… decode βœ… decode β€”
HEIC βœ… decode + encode βœ… decode + encode β€”
AVIF βœ… decode (+ encode on Apple Silicon) βœ… decode + encode β€”

JPEG, PNG, WebP, GIF, and BMP use statically linked codecs and produce identical output across all platforms. HEIC, AVIF, and TIFF use OS system backends (ImageIO + vImage on macOS, WIC on Windows) with lazy symbol resolution for zero startup cost.

Performance vs sharp 0.34.5

Benchmarked on linux/x64 with 50 iterations and sharp.concurrency(1)

:

Operation Bun.Image sharp Speedup
metadata() 0.004 ms 0.28 ms 70Γ—
1080p PNG β†’ 400Γ—400 β†’ JPEG 28.6 ms 39.5 ms 1.38Γ—
1080p PNG β†’ 800Γ—600 β†’ WebP 82.7 ms 110.1 ms 1.33Γ—
4K JPEG β†’ 800Γ—450 β†’ JPEG 35.8 ms 45.5 ms 1.27Γ—
4K JPEG β†’ 1920Γ—1080 β†’ JPEG 57.2 ms 69.9 ms 1.22Γ—
12MP JPEG β†’ 1024Γ—768 β†’ WebP 138 ms 165 ms 1.20Γ—

The performance comes from i16 fixed-point SIMD resize kernels, JPEG IDCT scaling to the smallest sufficient size, zero-copy ArrayBuffer borrowing, and a single pre-allocated arena for resize scratch memory.

Global Virtual Store

bun install --linker=isolated

now supports a shared global virtual store via the install.globalStore = true

option in bunfig.toml

. Instead of cloning every package from the cache into each project's node_modules

on every install, packages are materialized once into a global <cache>/links/

directory, and each project's node_modules/.bun/<pkg>@<ver>

becomes a symlink into it.

Warm installs β€” lockfile present, cache warm, node_modules

wiped (the common CI path) β€” now perform ~1 symlink()

per package instead of ~1 clonefileat()

per file copy. On macOS APFS, clonefileat()

holds a volume-wide kernel lock that made parallelization ineffective. The global store eliminates those calls entirely on the warm path.

Benchmarks β€” warm install of a ~1,400-package fixture on Apple Silicon macOS (hyperfine --warmup 3 --runs 10

):

Wall time System time clonefileat calls
--linker hoisted 823 ms 478 ms 1,387
--linker isolated (before) 841 ms 1,256 ms 1,387
--linker isolated (after) 115 ms 94 ms 0

This is still experimental, so the global store is off by default with the isolated linker.

To enable:

[install]
globalStore = true

Or via environment variable:

BUN_INSTALL_GLOBAL_STORE=1 bun install

A package is eligible for the global store only when it comes from an immutable cache source (npm registry, git, tarball β€” unpatched, no trusted lifecycle scripts) and all of its transitive dependencies are also eligible. Ineligible packages fall back to per-project copies automatically.

The entry hash encodes the package's resolved dependency closure, so two projects that resolve a package to the same transitive versions share one on-disk entry, while a project with different resolutions gets its own.

This release also fixes a pre-existing issue: Bun now synthesizes an implicit "*"

optional peer dependency for entries that appear in peerDependenciesMeta

but not in peerDependencies

(matching pnpm/yarn behavior). This fixes compatibility with packages like webpack-cli

.

HTTP/3 (QUIC) support in Bun.serve

Bun.serve

⚠️ Highly experimental. HTTP/3 support is new and likely has bugs. Do not deploy http3: true

to production yet.

Bun.serve

now supports HTTP/3 over QUIC. Enable it with a single flag:

Bun.serve({
  port: 443,
  tls: { cert, key },
  http3: true, // also listen on UDP/443 for HTTP/3
  fetch(req) {
    return new Response("hi");
  },
});

When http3: true

is set alongside tls

, Bun binds TCP for HTTP/1.1+2 and UDP for HTTP/3 on the same port. Your existing fetch

handler and routes

work identically across all three protocols β€” no code changes needed. HTTP/1.1 and HTTP/2 responses automatically include Alt-Svc: h3=":<port>"; ma=86400

so browsers discover the QUIC endpoint.

You can also serve HTTP/3 only:

Bun.serve({
  port: 443,
  tls: { cert, key },
  http3: true,
  http1: false, // disable HTTP/1.1
  fetch(req) {
    return new Response("h3 only");
  },
});

Everything you'd expect works over HTTP/3: new Response(readableStream)

for streaming, new Response(Bun.file("large.bin"))

, new Response(req.body)

passthrough, req.url

/req.headers

/req.method

across await

boundaries, requestIP()

, server.reload()

, and graceful server.stop()

.

Performance

On Linux x64 (single process, loopback), HTTP/3 is significantly faster than HTTPS/1.1 from the same server instance:

Benchmark HTTP/3 HTTPS/1.1 HTTP/1.1
Static route (routes ) 509,135 req/s 189,130 req/s 239,476 req/s
Dynamic fetch handler 283,485 req/s 142,323 req/s 171,696 req/s

~50% of HTTP/3 CPU time is inside lsquic; further optimizations may come in a future releas.e

Limitations

WebSocket over HTTP/3 is not supported yet (server.upgrade()

returnsfalse

). WebTransport is a separate project.0-RTT is disabled.unix:

socket addresses skip the H3 listener (QUIC over Unix sockets is non-standard).- No trailer support, no Expect: 100-continue

(matching HTTP/1.1 behavior).

Powered by lsquic v4.6.2.

Experimental HTTP/2 Client for fetch()

fetch()

fetch()

now supports HTTP/2 as an experimental feature. When enabled, Bun negotiates h2

via TLS ALPN β€” multiple concurrent fetches to the same origin share a single multiplexed TCP+TLS connection instead of opening separate HTTP/1.1 connections.

Enable it globally with an environment variable or CLI flag, or opt in per-request:

// Opt in globally:
//   BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT=1 bun run app.js
//   bun run --experimental-http2-fetch app.js

// Or per-request (works without the env flag):
const res = await fetch("https://example.com", { protocol: "http2" });

Multiplexing & connection coalescing

Parallel fetches to the same origin share one TLS handshake and one connection. The first request opens the socket; subsequent requests attach to the same HTTP/2 session up to the server's MAX_CONCURRENT_STREAMS

limit, with overflow queued automatically.

Per-request protocol control

The new protocol

option in RequestInit

lets you pin the HTTP version:

// Force HTTP/2 β€” fails with HTTP2Unsupported if the server doesn't support it
await fetch("https://example.com", { protocol: "http2" });

// Force HTTP/1.1 β€” ignores the experimental flag
await fetch("https://example.com", { protocol: "http1.1" });

Accepted values: "http2"

, "h2"

, "http1.1"

, "h1"

.

What works

Keep-alive poolingβ€” idle HTTP/2 sessions (with HPACK state) are reused by subsequent requests** Streaming request bodies**β€”ReadableStream

bodies are sent as DATA frames with proper flow controlβ€” transparently retried (up to 5 attempts) for replayable bodiesREFUSED_STREAM

and gracefulGOAWAY

Content-Length enforcement per RFC 9113 Β§8.1.1supportExpect: 100-continue

Hardening

The HTTP/2 client also includes RFC 9113 conformance and denial-of-service protections:

CONTINUATION flood / HPACK bomb mitigation: 256 KiB cap on both header-block accumulation and decoded header lists, advertised viaSETTINGS_MAX_HEADER_LIST_SIZE

.PING reflection attack mitigation: 1 MiB cap on queued PING/SETTINGS-ACK control frames prevents unbounded memory growth from malicious servers.- The first server frame must be SETTINGS per RFC 9113 β€” connections that violate this are immediately terminated. RST_STREAM(NO_ERROR)

mid-body now correctly fails the request instead of silently truncating the response.REFUSED_STREAM

retries only when no data has been delivered to the caller.- Content-Length mismatches with actual DATA frame bytes are now detected and rejected.

  • Trailers without END_STREAM

are now rejected per spec. GOAWAY

no longer drops already-completed streams.

Not yet supported

HTTP proxies/CONNECT tunneling, Unix sockets, server push, and cleartext HTTP/2 (h2c) are not yet supported. The HTTP/1.1 path is completely unchanged when the flag is off and protocol

is not set.

Experimental HTTP/3 Client for fetch()

fetch()

fetch()

now supports an experimental HTTP/3 client using the protocol

option. This uses an lsquic-backed QUIC transport that runs alongside the existing HTTP/1.1 and HTTP/2 paths.

⚠️ Highly experimental. This is an early preview β€” the API may change in future releases.

const res = await fetch("https://example.com/", { protocol: "http3" });
console.log(await res.text());

Both "http3"

and "h3"

are accepted as protocol values. The HTTP/3 client shares the same redirect, decompression, and response handling pipeline as HTTP/1.1 and HTTP/2, so existing fetch()

behavior is preserved.

What's supported:

  • All standard HTTP methods (GET, POST, PUT, DELETE, HEAD)
  • Request and response headers, JSON bodies, gzip compression
  • Redirects
  • Large request/response bodies (1 MB+ round-trips)
  • Concurrent multiplexed requests over a single QUIC connection
  • Connection pooling and sequential reuse ReadableStream

request body uploads- Full-duplex bidirectional streaming (server can respond while upload is still in progress) rejectUnauthorized

TLS option (defaults totrue

)AbortSignal

support

Alt-Svc HTTP/3 upgrades

The HTTP/3 client can also automatically upgrade fetch()

requests to HTTP/3 via the Alt-Svc

header (RFC 7838). When a server advertises Alt-Svc: h3

in an HTTPS response, subsequent requests to that origin are routed over QUIC instead of TCP.

This is opt-in while the HTTP/3 client matures. Enable it with the CLI flag or environment variable:

bun --experimental-http3-fetch app.ts

BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP3_CLIENT=1 bun app.ts
js
// First request goes over TCP/TLS as usual
const res1 = await fetch("https://example.com/api");
// If the response includes `Alt-Svc: h3=":443"`,
// the next request to the same origin uses QUIC/HTTP-3
const res2 = await fetch("https://example.com/api");

The upgrade is transparent and per-origin β€” cross-origin redirects re-evaluate from HTTP/1.1, and requests that aren't eligible (proxied, unix socket, sendfile

, or pinned to a specific protocol) gracefully fall back to TCP.

Rewritten fs.watch()

backend on Linux, macOS, and FreeBSD

fs.watch()

backend on Linux, macOS, and FreeBSDBun's fs.watch()

implementation on POSIX platforms has been completely rewritten to talk directly to the OS file-watching APIs (inotify, FSEvents, kqueue) instead of routing through Bun's internal bundler watcher. This fixes several long-standing bugs and reduces complexity significantly.

Recursive watching now tracks new directories (Linux)

Previously, fs.watch("dir", { recursive: true })

only registered the directory tree that existed at the time watch()

was called. Directories created after the watch started were never tracked, so files inside them were invisible to the watcher.

import fs from "node:fs";

// Now correctly detects changes inside directories created after watch() starts
fs.watch("./src", { recursive: true }, (event, filename) => {
  console.log(event, filename);
});

// mkdir src/newDir && touch src/newDir/file.txt
// Previously: only "rename newDir" β€” file.txt was missed
// Now: "rename newDir", "rename newDir/file.txt", "change newDir/file.txt"

Deleted-and-recreated files emit change

events again (Linux)

change

events again (Linux)When a watched file was deleted and recreated, subsequent modifications to the recreated file would silently stop emitting change

events. This is now fixed β€” the new inotify watch descriptor is correctly registered on recreation.

macOS no longer spins up two watcher threads

Previously, fs.watch()

on a directory on macOS would start both a kqueue watcher (via the bundler watcher) and an FSEvents CFRunLoop

thread. The new implementation uses FSEvents exclusively for both files and directories, matching libuv's behavior and halving the thread overhead.

--no-orphans

β€” exit when the parent process dies

--no-orphans

β€” exit when the parent process diesBun now supports an opt-in mode that automatically exits when its parent process dies β€” even if the parent was SIGKILL

ed and never had a chance to forward a signal. On exit, Bun also recursively SIGKILL

s every descendant process it spawned.

This is useful when Bun is launched by a supervisor (Electron, a CI runner, a thin shim) that may be force-killed. Without this option, Bun would be silently reparented to launchd

/init

and keep running forever, along with anything it spawned.

There are three equivalent ways to enable it:

bun --no-orphans run my-script

[run]
noOrphans = true

BUN_FEATURE_FLAG_NO_ORPHANS=1 bun run my-script

The flag is automatically inherited by nested Bun processes, so enabling it once at the top level is sufficient.

How it works:

Linux: Usesprctl(PR_SET_PDEATHSIG, SIGKILL)

β€” kernel-delivered, no polling, no thread. Children spawned from the main thread also inheritPDEATHSIG

by default so non-Bun descendants are covered.macOS: Registers aEVFILT_PROC

/NOTE_EXIT

watch for the original parent pid on the existing event loop's kqueue β€” the same mechanism Bun already uses to watch child process exits. No dedicated thread, no extra file descriptor.

On clean exit, Bun walks its process tree and uses a stop-verify-kill strategy for pid-reuse safety: each descendant is SIGSTOP

ped, its ppid is re-verified, and only then is it SIGKILL

ed. This prevents accidentally killing an unrelated process that recycled a stale pid.

macOS coverage is now comprehensive. Previously, bun run <script>

and bunx

on macOS had no parent-death watching β€” if the parent was killed, spawned scripts could be left orphaned. Bun now uses a dedicated kqueue

watcher for these paths, monitoring both the parent process and child stdio. bun run --filter

and bun run --parallel

on macOS are also now covered.

Linux macOS
bun <file> βœ… prctl βœ… Event loop watcher
bun run / bunx βœ… prctl βœ… kqueue watcher (new)
--filter / --parallel βœ… prctl βœ… MiniEventLoop watcher (new)

Linux and macOS only (no-op on Windows).

process.execve()

support

process.execve()

supportBun now implements process.execve(execPath, args, env), matching the API added in Node.js v24. This POSIX syscall replaces the current process image in-place β€” it never returns on success.

// Replace the current process with a new one
process.execve("/usr/bin/echo", ["echo", "hello from execve"], {
  PATH: process.env.PATH,
});

// ^ If successful, this line is never reached

Key details:

stdio is inheritedβ€” file descriptors 0/1/2 are preserved across the exec boundary, while all other descriptors are marked close-on-exec to prevent leaks.Signal mask is reset before callingexecve(2)

.- Throws ERR_WORKER_UNSUPPORTED_OPERATION

when called from a worker thread. - Throws ERR_FEATURE_UNAVAILABLE_ON_PLATFORM

on Windows. - Emits an ExperimentalWarning

once per process, matching Node.js behavior. - If execve

fails, the process prints the error to stderr and aborts (consistent with Node.js behavior, since process state has already been mutated).

Bun.Terminal

on Windows via ConPTY

Bun.Terminal

on Windows via ConPTYBun.Terminal

and Bun.spawn({ terminal })

now work on Windows, powered by the Windows ConPTY API (CreatePseudoConsole

). Previously, Bun.Terminal

was only available on macOS and Linux.

const terminal = new Bun.Terminal({
  cols: 80,
  rows: 24,
  onData(data) {
    process.stdout.write(data);
  },
});

const proc = Bun.spawn({
  cmd: ["cmd.exe", "/c", "echo", "hello from ConPTY"],
  terminal,
});

await proc.exited;
terminal.close();

Platform differences

The core behavior β€” child sees a TTY, write()

reaches the child's stdin, child output reaches the data

callback, resize()

updates the child's window size β€” is the same on every platform. A few details differ on Windows:

No termios on Windows.inputFlags

,outputFlags

,localFlags

, andcontrolFlags

always read as0

and setting them is a no-op.No echo without a child process. On POSIX, the kernel line discipline echoeswrite()

input back to thedata

callback even with no process attached. ConPTY has no line discipline, so input is buffered for the next reader.ConPTY re-encodes output. Thedata

callback receives semantically equivalent β€” but not byte-identical β€” escape sequences compared to what the child emitted. Colors and text are preserved; cursor-positioning sequences may be reordered or coalesced.

Thanks to @dylan-conway for the contribution!

using

/ await using

no longer lowered when targeting Bun

using

/ await using

no longer lowered when targeting BunBun's underlying JavaScript engine (JavaScriptCore) natively supports the Explicit Resource Management proposal (using

and await using

). Starting in this release, Bun no longer transpiles these declarations into __using

/ __callDispose

helper calls wrapped in try

/catch

/finally

when the target is Bun.

This applies to:

bun run

/bun <file>

Bun.Transpiler({ target: "bun" })

bun build --target=bun

(including--compile

and--bytecode

)

Other targets (browser

, node

) continue to lower using

as before.

Before:

// bun build --target=bun entry.js
var __using = (stack, value, async) => {
  /* ... */
};
var __callDispose = (stack, error, hasError) => {
  /* ... */
};
{
  let __stack = [];
  try {
    const x = __using(
      __stack,
      {
        [Symbol.dispose]() {
          /* ... */
        },
      },
      0,
    );
    console.log("hi");
  } catch (_catch) {
    var _err = _catch,
      _hasErr = 1;
  } finally {
    __callDispose(__stack, _err, _hasErr);
  }
}

After:

// bun build --target=bun entry.js
{
  using x = {
    [Symbol.dispose]() {
      /* ... */
    },
  };
  console.log("hi");
}

This also fixes a bug where using

inside a CommonJS module (.cjs

) would inject an ESM import … from "bun:wrap"

inside the CommonJS function wrapper, causing an Expected CommonJS module to have a function wrapper

error instead of the expected TypeError

for non-disposable values.

SIGHUP

and SIGBREAK

signal handling on Windows

SIGHUP

and SIGBREAK

signal handling on Windowsprocess.on('SIGHUP', …)

and process.on('SIGBREAK', …)

now correctly receive Windows console-control events, matching Node.js behavior:

Console event Signal
CTRL_CLOSE_EVENT (closing the console window) SIGHUP
CTRL_BREAK_EVENT (Ctrl+Break) SIGBREAK

Previously, these signal names were missing from Bun's Windows signal map, so registering a listener was treated as a plain EventEmitter

event β€” no uv_signal_t

was created, and the default handler would terminate the process immediately.

// Gracefully handle console window close on Windows
process.on("SIGHUP", () => {
  cleanup();
  process.exit();
});

// Handle Ctrl+Break
process.on("SIGBREAK", () => {
  console.log("Ctrl+Break received");
  process.exit();
});

Thanks to @ig-ant for the contribution!

WebSocket perMessageDeflate: false

now respected in upgrade requests

perMessageDeflate: false

now respected in upgrade requestsPreviously, setting perMessageDeflate: false

when creating a WebSocket connection was silently ignored β€” Bun always sent the Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

header in the upgrade request. This broke deployments where gateways or proxies reject upgrade requests that advertise unwanted extensions.

Now, passing perMessageDeflate: false

correctly suppresses the extension header, matching the behavior of Node.js and the ws

package.

const WebSocket = require("ws");

// Extension header is now correctly omitted
const ws = new WebSocket("ws://localhost:3000", {
  perMessageDeflate: false,
});

// Also works with globalThis.WebSocket
const ws2 = new WebSocket("ws://localhost:3000", {
  perMessageDeflate: false,
});

Additionally, if the server responds with a Sec-WebSocket-Extensions

header when the client did not offer any extensions, the handshake is now correctly failed per RFC 6455 Β§9.1 β€” matching upstream ws

behavior.

FreeBSD and Android Support

Bun now has 1st-party native builds for FreeBSD and Android.

Reduced memory usage for MongoDB & Mongoose

All TLS-using APIs in Bun β€” Bun.connect

, Bun.SQL

(Postgres & MySQL), Valkey, upgradeTLS

, new WebSocket()

, and node:tls

β€” now share a single native SSL_CTX

cache per VM. Connections with identical TLS configurations reuse the same SSL_CTX

instead of allocating a fresh one (~50 KB of BoringSSL state + cert/key parsing) per connection.

This is especially impactful for database connection pools: a Postgres or MySQL pool with sslmode=require

and N connections previously created N separate SSL_CTX

objects. Now it creates one.

import { SQL } from "bun";

// All connections in this pool now share a single SSL_CTX
const db = new SQL("postgres://user:pass@host/db?sslmode=require");

// These also share the same cached SSL_CTX since the config is identical
const conn1 = await Bun.connect({
  hostname: "example.com",
  port: 443,
  tls: true,
  socket: {
    /* ... */
  },
});
const conn2 = await Bun.connect({
  hostname: "example.com",
  port: 443,
  tls: true,
  socket: {
    /* ... */
  },
});

The cache is keyed by a SHA-256 digest of the TLS configuration fields. servername

and ALPNProtocols

are excluded from the digest (they're per-connection, not per-context), so Bun.connect({ tls: { servername: "x" } })

correctly shares the default SSL_CTX

with tls: true

.

This was the root cause behind long-standing memory leak reports when using tls.connect()

, Bun.connect({tls})

, socket.upgradeTLS()

, and any library built on top of them (MongoDB, Mongoose, mysql2, etc.). Under connection churn β€” Postgres pools, Redis, fetch keepalive expiry, MongoDB heartbeats β€” RSS would grow rapidly until the garbage collector eventually frees the context. Now it avoids allocating unnecessary duplicate contexts.

// Before: each iteration allocated a fresh SSL_CTX (~50 KB+)
for (let i = 0; i < 1000; i++) {
  const sock = tls.connect({
    host: "localhost",
    port: 5432,
    rejectUnauthorized: false,
  });
  sock.on("secureConnect", () => sock.destroy());
}
// RSS after: ~1 GB

// After: one shared SSL_CTX, RSS stays flat
// RSS after: ~168 MB

Upgraded JavaScriptCore engine

Bun's underlying JavaScript engine (JavaScriptCore) has been upgraded with 565 upstream commits, bringing numerous performance improvements, bug fixes, and new capabilities.

JavaScript performance & correctness

Faster async functionsβ€” When an async function returns a value without anyawait

, the returned promise is now optimized to avoid unnecessary overhead.Fasterβ€” NewArray.prototype.shift

fastShift

implementation for arrays.Fasterβ€”JSON.parse

for short stringsJSString

cells are now cached for short string values returned byJSON.parse

.Fasterβ€” Single-character checks now avoid resolving rope strings.String.prototype.startsWith

/endsWith

Fasterβ€” Optimized construction and improved external memory reporting forIntl.NumberFormat

creationIntl.NumberFormat

andIntl.PluralRules

.Fasterβ€” New fast path added.Array.prototype.indexOf

on NodeList

Bug fixes from upstream

  • Fixed Promise.prototype.finally

throwing inSpeciesConstructor

before callingthen

, matching spec behavior. - Fixed Object.defineProperties

Proxy trap ordering to match the spec. - Fixed megamorphic inline cache property ownership check.

  • Fixed TypedArray toSorted

/toReversed

/with

to correctly snapshot the span. - Fixed Intl.Segmenter

isWordLike

off-by-one error. - Fixed Intl.Locale

to canonicalize before overriding language. - Fixed Intl.DateTimeFormat

to preserve original legacy[[TimeZone]]

. - Fixed several RegExp JIT issues

  • Fixed JIT compiler issues with hole-handling when rematerializing sunk double arrays and escaping MultiGetByOffset

constants not convertible to double.

WebAssembly

Relaxed SIMD supportβ€” Implements therelaxed SIMD proposal, adding instructions likef32x4.relaxed_madd

,i8x16.relaxed_swizzle

, and more.Memory64 improvementsβ€” Atomics, bulk memory operations, and grow/size in the OMG tier now support 64-bit memory.- Fixed integer division/remainder with INT_MIN

/-1

in BBQ JIT. - Fixed floating-point min/max negative-zero handling in BBQ JIT.

  • Fixed crash on wide-arithmetic instructions.

Thanks to @sosukesuzuki for the upgrade!

bun publish

now sends README metadata to the registry

bun publish

now sends README metadata to the registryPreviously, bun publish

included README.md

in the published tarball but didn't populate the readme

or readmeFilename

fields in the JSON body sent to the npm registry. This meant packages published with Bun showed an empty README when queried via the registry API (e.g. npm view <pkg> readme

), even though the tarball contained one.

Now, bun publish

matches npm publish

behavior by automatically finding the first README

or README.*

file (case-insensitive) in your workspace and including its contents in the version metadata sent to the registry. This works for both workspace publishes (bun publish

) and tarball publishes (bun publish path.tgz

). A readme

field already present in package.json

takes precedence.

npm view @my-scope/my-package readme  # ""

npm view @my-scope/my-package readme  # "# my-package\n\nA great package..."

Updated SQLite to 3.53.0

Bun's built-in SQLite has been updated from 3.51.2 to 3.53.0.

Notable changes in SQLite 3.53.0 include:

  • New SQLITE_DBCONFIG_FP_DIGITS

option for controlling floating-point precision when converting doubles to text - New SQLITE_LIMIT_PARSER_DEPTH

limit for controlling the maximum depth of the SQL parser stack - New SQLITE_PREPARE_FROM_DDL

flag for enforcing schema-level security constraints during statement preparation

Cross-language LTO for Zig ↔ C++ on Linux

Bun's binary is now built with full link-time optimization (LTO) across the Zig and C++ boundaries on Linux. Previously, the Zig-compiled object file was a native ELF object that the linker could link but not optimize across β€” meaning hundreds of small cross-language function calls (Zig β†’ C++, C β†’ Zig, allocator calls) were never inlined.

By emitting the Zig object as LLVM bitcode and participating in the same LTO link pass as the C/C++ side, LLVM can now inline and optimize across language boundaries:

Boundary Functions declared Functions eliminated by inlining %
Zig export fn β†’ C++ 336 142 42%
C us_* (Β΅Sockets) ← Zig 115 79 69%
C++ uws_* (Β΅WebSockets) ← Zig 108 76 70%
mi_free (mimalloc) β€” all 100%

Measured impact (linux-x64):

Benchmark Before After Improvement
Bun.escapeHTML 183.2 ns 171.3 ns 6.5% faster
TextDecoder.decode 106.8 ns 104.0 ns 2.6% faster
HTTP throughput (oha -n 1M -c 50 ) ~193,800 req/s ~200,600 req/s 3.5% faster

This is a broad improvement β€” any hot path that crosses the Zig/C++ boundary benefits, including HTTP serving, text encoding/decoding, and HTML escaping.

Faster ESM module

Fixed an internal parser oversight where an ~8KB struct was being copied by value on every AST node allocation, causing unnecessary memcpy

overhead during transpilation. Passing it by pointer instead eliminates the redundant copies, reducing _platform_memmove

overhead from 7.5% to 2.9% of self time in profiling.

On a benchmark 500 ESM files, this results in approximately 12% faster module (~140ms β†’ ~123ms).

Thanks to @sosukesuzuki for the contribution!

Reduced GC overhead for built-in objects

Bun's incremental garbage collector previously re-scanned ~63 types of built-in objects (Request

, Response

, Subprocess

, Stats

, Dirent

, Timeout

, and more) after every mutator yield during incremental GC β€” even though these objects already use write barriers that guarantee correctness without the extra pass.

This redundant work has been removed. Only visitChildren

is called now for codegen'd classes, eliminating the overhead of re-walking every live instance of these common types during incremental GC cycles. Hand-written types that genuinely require output constraints (like EventTarget

, AbortSignal

, MessagePort

, etc.) are unchanged.

This should reduce GC times, especially in applications with many live built-in objects.

Smaller binary size

Bun gets smaller on Windows and Linux. macOS binary size hasn't changed much.

Target
Linux aarch64 -9.07 MB
Linux aarch64-musl -7.63 MB
Linux x64 -8.58 MB
Linux x64-baseline -8.64 MB
Linux x64-musl -6.61 MB
Linux x64-musl-baseline -6.75 MB
Windows aarch64 -18.42 MB
Windows x64 -17.66 MB
Windows x64-baseline -17.67 MB

tls.getCACertificates('system')

now works without --use-system-ca

tls.getCACertificates('system')

now works without --use-system-ca

Previously, tls.getCACertificates('system')

returned an empty array []

unless --use-system-ca

or NODE_USE_SYSTEM_CA=1

was explicitly set. Node.js returns the OS trust store unconditionally for 'system'

β€” the flag only affects 'default'

. Bun now matches this behavior.

System certificates are lazy-loaded on first demand, so there's no startup cost unless 'system'

is actually queried or --use-system-ca

is set.

import tls from "node:tls";

// Previously returned [] without --use-system-ca, now returns system CA certs
const systemCerts = tls.getCACertificates("system");
console.log(systemCerts.length); // > 0 on Linux/Windows

This also fixes a data race that could cause segfaults or truncated certificate lists when multiple threads (e.g. Workers) accessed root certificates concurrently during initialization.

Thanks to @cirospaciari for the contribution!

tls.getCACertificates('system')

no longer stalls on managed Macs

tls.getCACertificates('system')

no longer stalls on managed MacsOn macOS, tls.getCACertificates('system')

previously evaluated every keychain certificate using SecTrustEvaluateWithError

with an SSL policy, causing trustd

to attempt OCSP/CRL/AIA network fetches for each cert. On managed Macs running a NetworkExtension content filter, this turned a local lookup into ~10 seconds of wall-clock time as hundreds of outbound flows were individually signed and policy-denied by the filter.

This release rewrites the macOS keychain enumeration to match how Node.js and Chromium handle it:

Removedβ€” this flag forced a redundant network-revocation-enabled evaluation of every cert before per-cert filtering even started.kSecMatchTrustedOnly

from the keychain queryReplaced the trust-settings stub with a full parserβ€” the previous implementation always returnedUNSPECIFIED

, causing every cert to fall through to the expensiveSecTrustEvaluateWithError

path. The new parser (ported from Node'sIsTrustSettingsTrustedForPolicy

) resolves certs via cheap local XPC lookups.Deferredβ€” only certs with no decisive trust settings in any domain reach it, and when they do, it now usesSecTrustEvaluateWithError

as a last resortSecPolicyCreateBasicX509

+SecTrustSetNetworkFetchAllowed(false)

to avoid network access entirely.

The result is functionally equivalent β€” OpenSSL still enforces EKU and basic-constraint checks at handshake time β€” but enumeration no longer triggers any network I/O.

--use-system-ca

on Windows now loads intermediate and TrustedPeople certificates

--use-system-ca

on Windows now loads intermediate and TrustedPeople certificatesWhen using --use-system-ca

or NODE_USE_SYSTEM_CA=1

on Windows, Bun now reads from the same certificate stores as Node.js, fixing unable to get local issuer certificate

errors commonly seen in enterprise and intranet environments.

Previously, Bun only enumerated the ROOT

store for CURRENT_USER

and LOCAL_MACHINE

. This meant that when a server only sent a leaf certificate without its intermediates (very common on intranets with corporate proxies or self-signed certificates), Bun couldn't build the certificate chain β€” even though Windows had the intermediates cached in its CA

store.

Bun now mirrors Node.js's ReadWindowsCertificates

behavior:

Before After
Store names ROOT ROOT , CA , TrustedPeople
Locations LOCAL_MACHINE , CURRENT_USER + GROUP_POLICY , ENTERPRISE variants
CERT_STORE_OPEN_EXISTING_FLAG βœ— βœ“ (don't create missing stores)
EKU server-auth filter βœ— βœ“ (skip certs restricted to e.g. code-signing only)

This brings --use-system-ca

on Windows to parity with Node.js, making it significantly more reliable for enterprise environments with custom certificate authorities and proxy servers.

Event loop refactor

Large parts of the event loop have been refactored to improve reliability and simplify memory management.

Along the way, this fixed:

DuplexUpgradeContext

was never freed (full leak pertls.connect({socket: duplex})

)UpgradedDuplex.onEndCallback

was incorrectly wired toonReceivedData

SSLWrapper.init

leaked the strdup'd passphrase on error pathsTLSSocket.memoryCost

now correctly reports off-heap SSL state

Bugfixes

Node.js compatibility improvements

  • Fixed: memory leak in node:http

whereNodeHTTPResponse

and its associated buffers were never freed whenondata

was re-registered after the request body had already been fully received, also causing the event loop to stay alive unnecessarily - Fixed: res.setTimeout()

on client-sideIncomingMessage

no longer keeps the event loop alive after the response completes. Previously, callingres.setTimeout(90000)

would prevent the process from exiting for the full timeout duration, even when there was nothing left to do. The timer is now unref'd to match Node.js behavior. Also fixedres.setTimeout()

to returnthis

, clear the timer when called with0

, and stack listeners viares.on("timeout", cb)

for Node.js compatibility. - Fixed: use-after-free crash in HTTPS requests when checkServerIdentity

rejects a certificate due to hostname mismatch - Fixed: checkServerIdentity

callback passed tohttps.request()

was ignored β€” the native check always ran instead - Fixed: https.createServer()

with aca

option incorrectly required client certificates, even whenrequestCert

was not set totrue

  • Fixed: TLS certificate identity verification now falls back to the Subject Common Name (CN) when the certificate has no SAN entries, matching Node.js behavior
  • Fixed: use-after-free crash in node:zlib

when anonerror

callback issued a re-entrantwrite()

followed byclose()

on native zlib/brotli/zstd handles - Fixed: heap-use-after-free crash in node:zlib

when calling.reset()

on a zlib, Brotli, or Zstd stream while an async.write()

is still in progress on the threadpool - Fixed: memory leak in crypto.scrypt

where the callback and protected password/salt buffers were never released when the output buffer allocation failed (e.g. with an extremely largekeylen

) - Fixed: crypto.randomFill

andcrypto.randomFillSync

bounds-checking bugs that could cause a heap overflow whenoffset

exceeded 2²⁴ due tof32

precision loss, and a unit mismatch that caused integer underflow or silent under-fill for multi-byte typed arrays (e.g.Float64Array

) when using the 3-argument form - Fixed: crypto.subtle.unwrapKey('jwk', ...)

promise never settling and leaking memory when the decrypted payload was valid JSON but not a valid JWK (e.g. missing the requiredkty

field). TheTypeError

now correctly rejects the promise instead of escaping as an uncaught exception. Also fixed a smaller native memory leak when decrypted bytes weren't valid JSON at all. - Fixed: process.dlopen

crash when a native addon's init callback re-entrantly callsnapi_module_register()

(e.g. nesteddlopen

or registering additional modules from within an init function), which could invalidate the internal iterator and cause a use-after-free - Fixed: napi_create_external_arraybuffer

andnapi_create_external_buffer

now correctly returnnapi_pending_exception

when a NAPI exception is already pending, matching Node.js behavior. Previously, calling these functions with a pending exception could lead to double-frees or orphaned GC cells with permanently-disarmed destructors. - Fixed: fuzzer-detected crash in process.setgroups()

andprocess.hrtime()

with unexpected input - Fixed: crash when lazy construction of process.stdin

/process.stdout

/process.stderr

throws near the stack recursion limit and anuncaughtException

listener is registered - Fixed: process.stdin

hanging or spinning at 100% CPU when reading from a FIFO pipe and the parent process dies or a new writer reappears during the drain loop - Fixed: process.stdin.isRaw

not updating after a successfulsetRawMode()

call on Windows, which causedreadline

and other modules that checkisRaw

to incorrectly restore cooked mode - Fixed: out-of-bounds read in Buffer.from()

with invalid input in a less common encoding - Fixed: Buffer.copyBytesFrom()

producing incorrect results or throwingRangeError: Out of memory

when called with a TypedArray view that has a non-zerobyteOffset

into its backingArrayBuffer

  • Fixed: memory leak in Buffer.from(string, 'hex')

andBuffer.from(string, 'base64')

when the input contained no valid encoded characters (e.g.Buffer.from('zz', 'hex')

). The internal staging allocation was never freed when decoding produced zero bytes, causing ~4KB to leak per call. Also fixed a related issue whereffi.toBuffer(ptr, 0, finalizer)

would silently drop the user's finalizer. - Fixed: crash and out-of-bounds read in Buffer#copy

andBuffer#fill

when avalueOf

callback detaches or resizes the underlyingArrayBuffer

during argument coercion - Fixed: memory leak in child_process

stdout when reading from spawned processes β€” the internalFileReader.onPull

memcpy path failed to free the drained buffer, causing linear RSS growth under sustained reads - Fixed: memory leak in tlsSocket.setSession()

where each call leaked oneSSL_SESSION

(~6.5 KB per call) due to a missingSSL_SESSION_free

afterd2i_SSL_SESSION

  • Fixed: crash when calling TLSSocket.getServername()

after the socket was closed, due to a null SSL pointer dereference - Fixed: use-after-free in tls.connect({socket: duplex})

when a pre-open duplex error races with the queuedStartTLS

task, causing freedHandlers

memory to be read - Fixed: memory leaks in getPeerCertificate()

on server-side TLS sockets (mTLS) where an X509 reference fromSSL_get_peer_certificate

was never freed and a BIO was leaked (~800 bytes per call) inX509Certificate.raw

due to an incorrect destructor capture - Fixed: memory leak in node:net

whensocket.connect()

fails synchronously on reused handles (e.g. connecting to a nonexistent Unix socket path), which leaked one native socket struct per failed reconnect - Fixed: memory leak where fs.watch(path, { persistent: false })

watchers were never garbage collected after.close()

, caused by a reference count underflow that permanently pinned each watcher as a GC root - Fixed: memory leak in fs.watch()

on macOS where the resolved directory path was never freed, leaking ~path-length bytes on everyfs.watch(<directory>)

call - Fixed: a use-after-free race condition in fs.watch

on macOS where closing a watcher while events were firing could crash due to the CoreFoundation thread reading freed memory - Fixed: a crash on macOS where FSEventStreamCreate

could returnNULL

under rapidfs.watch().close()

churn, causing a CoreServices crash when theNULL

stream was passed toFSEventStreamScheduleWithRunLoop

  • Fixed a crash in fs.readdirSync()

with{ encoding: 'buffer', recursive: true }

when a subdirectory fails to open (e.g. due to a self-referential symlink causingELOOP

). An internal memory management bug caused a use-after-free in the error cleanup path, leading to crashes or corrupted results. - Fixed: memory leak in fs.readdirSync()

with{ recursive: true, withFileTypes: true }

when the call fails partway through (e.g. due toELOOP

orEACCES

), whereDirent.path

references were not properly released on the error path - Fixed: crash involving long file paths in fs.cp

,fs.promises.cp

, andfs.cpSync

with{ recursive: true }

  • Fixed: fs.cp

/fs.cpSync

on Linux and FreeBSD copied symlinks with the source symlink's own path as the target instead of the path the symlink actually pointed at, causing copied symlinks to point back into the source tree and dangle if the source was deleted. Also fixedcpSync

throwingENOENT

when copying a symlink to a destination whose parent directory doesn't exist. - Fixed: fs.cp

/fs.cpSync

on Windows leaking an OS handle for every symlink or junction in the source tree, which could exhaust the process handle table when copying large trees (e.g.node_modules

with pnpm-style junctions) - Fixed: memory leak in fs.symlinkSync

,fs.linkSync

, andfs.renameSync

where path arguments were not freed when a later argument was rejected, causing RSS to grow by hundreds of MB in error-heavy loops - Fixed: memory leak in dns.lookup

anddns.resolve*

when more than 32 concurrent c-ares DNS requests are in flight, where overflow results were never freed - Fixed: use-after-free crashes in node:http2

when re-entrant JS callbacks (e.g.session.request()

inside a timeout listener, an options getter, or a write callback) triggered a hashmap rehash, invalidating internal stream pointers. Streams are now heap-allocated and stable for the lifetime of the session. (@Jarred-Sumner) - Fixed: a crash in node:vm

whereSourceTextModule.link()

with many imports could trigger a use-after-free when the garbage collector concurrently iterated the module's resolve cache during a HashMap rehash - Fixed: ESM module evaluation where sibling static imports could incorrectly skip waiting for async-pending strongly connected components, causing TDZ errors when accessing bindings from cyclic module graphs with top-level await

  • Fixed: deadlock when a non-entry module with top-level await

dynamically imports a module that imports it back - Fixed: importing a module with top-level await

from multiple sibling imports in the same module graph caused aReferenceError: Cannot access before initialization

because sibling modules skipped the spec-mandated wait for the shared TLA dependency to settle (@sosukesuzuki) - Fixed: node:test

top-leveltest()

ignoring{ skip }

and{ todo }

options β€” nestedSuite#test

honored them, but the top-level entry point did not - Fixed: child processes spawned by Bun on macOS could inherit an effectively infinite RLIMIT_NOFILE

soft limit, causing programs that read the limit into anint

to misbehave (e.g. failing socket reads). The startup file descriptor limit raise is now capped at1 << 20

, matching Node.js. (@alii)

Bun APIs

  • Fixed: use-after-free when reading .listener

on a closed client socket created viaBun.connect()

  • Fixed: use-after-free when accessing handle.listener

on a socket that failed to connect, where the native socket'shandlers

pointer was left dangling after the per-connectionHandlers

allocation was freed - Fixed a crash when calling socket.reload()

orlistener.reload()

on active TCP/TLS sockets. Previously, re handlers reset the internal active connection counter to zero, which caused integer overflow panics in debug builds and segfaults (heap-use-after-free) in release builds. This affected reloads both inside and outside of socket event handlers. - Fixed: crash (NULL dereference / SIGSEGV) when closing a socket inside a TLS 1.2 renegotiation handshake callback ( Bun.connect

with TLS) - Fixed: use-after-free in TLS server ALPN callback when handling concurrent handshakes, which could cause crashes or garbage data during protocol negotiation on servers with ALPNProtocols

configured (@Jarred-Sumner) - Fixed: TLS sockets could leak memory and stay open indefinitely when SSL_shutdown

failed to flush theclose_notify

alert (e.g. kernel buffer full or peer already gone), because the socket would wait for a peer event that never arrives. Now the socket closes immediately in this case. (@cirospaciari) - Fixed: use-after-free crash when calling flush()

on a TLS socket afterend()

, where the deferred TLS close_notify handshake could dereference already-freed handler memory - Fixed: DNS resolution hanging indefinitely on macOS when retrying without AI_ADDRCONFIG

on loopback-only network configurations. The retry path was polling the wrong mach port, causingfetch()

,Bun.connect()

, and other network APIs to hang instead of completing the DNS lookup. - Fixed: Bun.dns.lookup()

with an oversized hostname could cause a crash - Fixed: DNS resolution over TCP could trigger an assertion failure when c-ares requested both readable and writable polling on the same socket file descriptor. FilePoll

now correctly merges both directions into a singleepoll_ctl(CTL_MOD)

call on Linux, and properly submits twoEV_DELETE

changes on kqueue (macOS/FreeBSD) when both directions are registered. (@Jarred-Sumner) - Fixed: when using Bun.spawn

withstdin: "pipe"

without ever reading the.stdin

property, the stdin pipe file descriptor would leak and remain open until theSubprocess

was garbage collected β€” even after the child process had already exited. The stdin pipe fd is now properly closed when the child process exits. - Fixed: Bun.spawn

incorrectly closing caller-owned file descriptors passed as extrastdio

slots (index β‰₯ 3) after the subprocess was garbage collected, causingEACCES

/EBADF

errors on subsequent reuse of those descriptors (@cirospaciari) - Fixed: Bun.spawn

returningnull

forproc.stdio[N]

when a caller-supplied file descriptor was passed as extra stdio (index β‰₯ 3), instead of returning the fd number back. Caller-owned fds are now correctly exposed viaproc.stdio[N]

while still never being closed by the subprocess. - Fixed: Bun.spawn

reading uninitialized memory whenstdio[N>=3]

isundefined

or a sparse array hole, which could expose garbage file descriptors or crash by dereferencing invalid pointers in release builds - Fixed: memory leak where Bun.spawn()

subprocess objects were never garbage collected when stdout/stderr pipes drained asynchronously after the child process exited (e.g. when a grandchild process inherited the pipe) - Fixed: Bun.spawn({ ipc })

subprocesses were never garbage collected after the child exited, leaking the subprocess along with its stdout/stderr buffers, stdin FileSink, and other retained objects for the lifetime of the process - Fixed: memory leak in subprocess PipeReader

when a stdout/stderr read failed with a real error (e.g.EBADF

,EIO

). ThePipeReader

struct, its buffered bytes,FilePoll

, and pipe fd would all leak, and the leaked poll's keep-alive ref would prevent the event loop from ever exiting. - Fixed: subprocess 'exit'

event not firing on Linux when multiple child processes exit simultaneously withstdio: 'ignore'

. On Linux, the pidfd poll was registered withEPOLLONESHOT

, which caused the kernel to disarm the fd before user-space could process it if a nested event loop tick occurred. The pidfd is now registered as level-triggered so a dropped event is harmlessly re-delivered. - Fixed: 2 rare crashes in Bun.serve()

  • Fixed: memory leak in Bun.serve()

when a directReadableStream

handler writes synchronously without returning a promise β€” each such request leaked the internal response sink and its buffer (~400 bytes per request) - Fixed: GC root leak in Bun.serve()

when a"direct"

response stream rejects while acontroller.end()

orcontroller.flush(true)

promise is pending due to transport backpressure - Fixed: heap-use-after-free and RequestContext

leak when a chunked request body exceedsmaxRequestBodySize

and the fetch handler returns a pending Promise - Fixed: memory leak in server.fetch(string)

where the intermediate URL buffer was leaked on every call - Fixed: server.fetch()

crashing with a segfault when passed aBigInt

argument, now properly rejects with aTypeError

  • Fixed: memory leak when calling server.reload()

with a WebSocket config that lacksopen

ormessage

handlers β€” discarded handler functions were permanently rooted and never garbage collected - Fixed: crash in Bun.serve()

whenwebsocket.perMessageDeflate

was set to a non-boolean primitive (e.g. number, string, bigint, or symbol). Now throws aTypeError

with a descriptive message instead. - Fixed: Bun.serve

withdevelopment: true

and a[serve.static]

plugin whosesetup()

throws would cause requests to hang forever instead of returning an error - Fixed: rare crash in server.upgrade()

when an option getter (e.g.get data()

) mutatesreq.headers

during the upgrade - Fixed: server.upgrade()

crashing or firing theopen

handler twice when a user-defined getter on thedata

orheaders

option re-entrantly calledserver.upgrade()

on the same request - Fixed: Bun.FileSystemRouter

crashing with a panic when route filenames contained certain byte values - Fixed: memory leak in Bun.FileSystemRouter

where accessing.params

on aMatchedRoute

  • Fixed: rare Bun.FileSystemRouter

use-after-free crash - Fixed: rare heap corruption in Bun.file().json()

  • Fixed: memory leak in Bun.zstdDecompressSync

where the partial output buffer was not freed when streaming decompression failed (e.g. corrupt or truncated zstd frames), and a double-free when known-size fast-path decompression failed - Fixed: file descriptor leak in Bun.Glob

when encounteringNAMETOOLONG

errors during directory traversal (@alii) - Fixed: Bun.pathToFileURL()

crashing with an out-of-bounds panic when given a relative path that, when joined with cwd, exceeded 4096 bytes - Fixed: memory leak in Bun.password.hash()

andBun.password.hashSync()

where the hash output buffer was not freed after being copied into a JavaScript string - Fixed: crash in Bun.markdown.ansi()

when input contained invalid UTF-8 lead bytes (lone continuation bytes0x80-0xBF

and bytes0xF8-0xFF

) by treating them as replacement characters instead of passing them to the multibyte decoder - Fixed: Bun.S3Client({ queueSize })

panicking whenqueueSize

exceeded 255, and silently overriding any validqueueSize

(1–255) to 255 - Fixed: Bun.s3.list()

panicking whenprefix

,delimiter

,continuationToken

, orstartAfter

exceeded ~341 characters after URL-encoding, since S3 keys can be up to 1024 bytes and percent-encoding can triple that size. - Fixed: Bun.Archive.prototype.files()

memory leak when processing corrupted/truncated archives where previously-read entries were not freed on mid-streamreadData

failures - Fixed: Bun.RedisClient

getting permanently stuck in a failed state after reconnection attempts were exhausted,close()

was called, or a fatal socket error occurred. Previously, callingclient.connect()

would not recover the client. Nowconnect()

properly resets internal state and replays the handshake, allowing the client to recover without replacing the instance. - Fixed: RedisClient

TLS connections now properly verify hostnames against the server certificate whenrejectUnauthorized: true

is set. Previously, hostname mismatches and self-signed certificates were silently accepted due to unreachable verification code paths. Connections with mismatched hostnames now correctly reject withERR_TLS_CERT_ALTNAME_INVALID

, and untrusted certificates reject with the appropriate verification error. - Fixed: FileSink

memory leak where native instances were never freed when a pending buffered write failed (e.g.EPIPE

after the reader closes), causing theFileSink

, its outgoing buffer, and associated refs to leak for the lifetime of the process - Fixed: memory leak when passing Bun.file()

ascert

,key

, orca

in TLS options β€” each config parse leaked one buffer per file - Fixed: crash in new Bun.Terminal()

when passing a non-object argument (e.g. a number, string, or boolean) instead of an options object - Fixed: Bun.udpSocket()

leaked memory when creation failed (e.g. invalid port, bind failure, or throwing getter) because the internal strong reference prevented garbage collection of the wrapper object - Fixed: a use-after-free in UDPSocket.send()

andUDPSocket.sendMany()

where user code invalueOf()

ortoString()

callbacks could detach an ArrayBuffer (via.transfer()

) between payload capture and the actual send, causing reads from freed memory - Fixed: heap out-of-bounds write in UDPSocket.sendMany()

when the socket's connection state changed mid-iteration via user JS callbacks (e.g.valueOf()

, array index getters). This could cause memory corruption or crashes whenconnect()

ordisconnect()

was called synchronously during asendMany()

call. - Fixed: UDPSocket.setTTL()

andUDPSocket.setMulticastTTL()

crashing with a null pointer dereference when the argument'svalueOf

closes the socket during coercion - Fixed: import("bun:main")

was incorrectly resolved as the npmmain

package instead of the built-inbun:main

module, due to a missing alias mapping in the runtime transpiler (@dylan-conway) - Fixed: use-after-free crash in HTMLRewriter.transform()

when a document or element handler returns a rejected promise during the finalend()

chunk - Fixed: memory leak in HTMLRewriter

where handler structs allocated by.on()

and.onDocument()

were never freed when the rewriter was garbage-collected, causing unbounded memory growth - Fixed: HTMLRewriter

attribute iterators could read freed memory when saved outside an element handler callback β€” calling.next()

on a leaked iterator now safely returns{done: true}

instead of dereferencing a dangling pointer. Iterators are also correctly detached whensetAttribute

orremoveAttribute

mutates the underlying attribute buffer.

bun:sql

bun:sql

  • Fixed: bun:sql

PostgreSQL connections that entered a.failed

state (e.g.ECONNREFUSED

, SSL refused withsslmode=require

, or a normalclose()

) were never garbage collected, leaking the entire native connection including buffers, statements, and SSL state. The request queue's backing buffer was also not freed during cleanup. - Fixed: sql.unsafe()

with multiple semicolon-separated statements in simple mode returned wrong column names for all result sets after the first, and leaked memory from previous field descriptors - Fixed: memory leak in bun:sql

when querying PostgreSQL array-typed columns (text[]

,int8[]

,json[]

,bytea[]

,bool[]

, etc.) that caused RSS to grow ~72 MB per 1,000 iterations instead of stabilizing after warmup - Fixed: a potential heap buffer overflow when parsing binary-format int4[]

/float4[]

arrays from a malicious or buggy PostgreSQL server. The server-providedlen

field is now validated against the actual column byte length before iterating, preventing out-of-bounds reads and writes. - Fixed: MySQL stored procedures called via prepared statements (tagged templates or sql.unsafe(..., params)

) would resolve after only the first result set, causing the trailing OK packet to surface as an unhandledTypeError

outside the caller'scatch

block - Fixed: heap buffer overflow in sql

MySQL client when the user-supplied parameter array was mutated (e.g. via a side-effecting getter) between query preparation and binding, which could cause out-of-bounds writes in release builds - Fixed: MySQL sql

client returning garbage error messages when a cached failed prepared statement was re-executed, caused by a dangling pointer into an overwritten socket read buffer - Fixed: MySQL .raw()

returning length-prefix bytes in the buffer for length-encoded columns (JSON, VARCHAR, TEXT, BLOB, ENUM, SET, GEOMETRY, NEWDECIMAL), causing garbled output when decoded as UTF-8 - Fixed: MySQL client could panic or silently read out-of-bounds memory when a server sent a short auth nonce during AuthSwitchRequest

formysql_native_password

. The client now validates nonce length and rejects withERR_MYSQL_MISSING_AUTH_DATA

before accessing the buffer. Also fixed potential divide-by-zero with empty nonces forcaching_sha2_password

and invalid indexing with empty public key payloads. - Fixed: a use-after-free crash in bun:sql

MySQL client when a prepared statement's column reallocation failed - Fixed: MySQL BLOB parameters could be corrupted when ArrayBuffer.transfer()

or GC occurred during query parameter binding - Fixed: memory leak in MySQL adapter when using dynamic interpolation in SQL template literals

  • Fixed: a crash in Bun.sql

MySQL client when a query's.catch()

callback calledconnection.close()

  • Fixed: SSL_CTX

leak in Postgres and MySQL connections when path coercion throws after SSL context creation - Fixed: us_listen_socket_add_server_name

not propagating duplicate-hostname errors, preventing App.h's rollback from firing

Web APIs

  • Fixed: FormData

multipart boundary format now matches WebKit exactly (----WebKitFormBoundary{hex}

with 4 leading dashes and capitalK

), fixing compatibility issues with downstream multipart parsers including OpenAI's API - Fixed: memory leak in FormData

serialization when aBun.file()

entry fails to read (e.g.ENOENT

). Previously, constructing aResponse

orRequest

from aFormData

containing a validBun.file()

followed by an invalid one would leak the already-read file buffers on each failed attempt. - Fixed: TextDecoder.decode

reading through a stale pointer when anoptions.stream

getter detaches or transfers the inputArrayBuffer

, which could cause incorrect output, non-deterministic behavior, or crashes due to heap corruption - Fixed: memory leak in TextDecoder

when decoding UTF-16LE and UTF-16BE encoded buffers. Each successful call to.decode()

leaked the decoded output buffer. - Fixed: empty Blob

andFile

objects incorrectly displaying as[Blob detached]

/[File detached]

inconsole.log

and afterstructuredClone

  • Fixed: use-after-free in Blob

when a duplicated blob'scontent_type

was heap-allocated, causingResponse

headers to read freed memory and return garbage values (e.g. afterBun.file(path, { type: "..." })

followed bynew Response(file)

) - Fixed: memory leak in structured-clone deserialization of Blob/File objects where truncated or malformed payloads would leak allocated buffers for content_type

, bytes payload,Store

, and heapBlob

on error paths - Fixed: structuredClone()

crash when serializing largeArrayBuffer

,SharedArrayBuffer

  • Fixed: memory leak in fetch()

when following long HTTP redirect chains - Fixed: memory leak when using fetch()

with percent-encodeddata:

URLs, where the intermediate decoded buffer was never freed on each call - Fixed: fetch()

silently hanging against certain hosts due to ECH GREASE being enabled in the TLS ClientHello. Some servers and middleboxes treated theencrypted_client_hello

extension as hostileβ€”completing the TLS handshake but never sending a response. This aligns Bun'sfetch

TLS behavior withcurl

, Node.js, and Bun's ownnode:tls

. - Fixed: WebSocket.close()

andWebSocket.terminate()

called during theCONNECTING

state would leave the socket permanently stuck inCLOSING

, never fireclose

/error

events, prevent the process from exiting, and leak the WebSocket instance. Now correctly transitions toCLOSED

, fireserror

thenclose

(code1006

,wasClean: false

) per the spec, and releases all internal references. - Fixed: memory leak in WebSocket

when TLS options were provided but the connection failed during option parsing or validation - Fixed: memory leak where every new WebSocket("wss://…")

routed through an HTTP CONNECT proxy (tunnel mode) leaked one internalHTTPUpgradeClient

struct due to a missing reference count release - Fixed: per-connection memory leak when using WebSocket clients over wss://

through an HTTP CONNECT proxy (tunnel mode). The internal I/O-layer reference was never released because the tunnel path doesn't adopt a uSockets socket, so the close handler never fired β€” leaking send/receive FIFOs, deflate state, and poll refs for every connection. - Fixed: a TypeError [ERR_INVALID_STATE]: Controller is already closed

error thrown when streaming small files (e.g.Bun.file(small).stream()

) where the first pull returns data and EOF simultaneously, causing the controller'sclose()

to be called twice - Fixed: a race condition where concurrent ReadableStream

instances (e.g.process.stdin

andfetch(file://...)

bodies) could close each other due to a shared mutablecloser

array at the class factory scope. This causedstdin

to be spuriously closed, breaking subsequent operations likesetRawMode

. - Fixed: concurrent ReadableStream

instances (e.g.Bun.stdin.stream()

andfetch(file://...)

bodies) could spuriously close each other due to a shared mutable EOF flag, causingstdin

to close unexpectedly withEBADF

errors - Fixed: TransformStream

instances that were dropped without being explicitly closed, errored, or aborted were never garbage collected, causing an out-of-memory crash in long-running applications. A GC-root cycle between the global object's guarded objects set and the internal writable stream kept the entire stream graph permanently reachable. - Fixed: a long-lived AbortSignal

reused across manyaddEventListener

/removeEventListener

cycles would accumulate dead closures in memory indefinitely. Each call toaddEventListener

with a{ signal }

option registered an internal abort algorithm, but removing the listener (viaremoveEventListener

,{ once: true }

firing, orremoveAllEventListeners

) never cleaned up that algorithm β€” causing unbounded memory growth on the signal. - Fixed: A non-303 redirect with a ReadableStream

body now correctly rejects withTypeError

instead ofUnexpectedRedirect

. - Fixed: AbortSignal.reason

is now properly forwarded to the request body'sReadableStream.cancel(reason)

.

Security

  • Fixed: HTTP request smuggling attack vector
  • Fixed: missing bounds check in maliciously-crafted Blob

deserialization - Fixed: integer overflow in IPC advanced

serialization mode with malicious input

Worker fixes

  • Fixed: stack overflow crash when closing a deep chain of nested transferred MessagePort

s - Fixed: MessagePort

memory leak when workers are terminated without explicitly closing their ports. Whenport.onmessage

was assigned orport.ref()

was called inside a Worker, the internal self-reference was never released during worker teardown, causing every suchMessagePort

to leak for the lifetime of the process. - Fixed: a race condition crash in MessageEvent

when usingBroadcastChannel

orMessagePort

where the GC marker thread could observe a torn variant inm_data

during concurrent access, causing aSIGSEGV

in release builds - Fixed: segfault in worker.getHeapSnapshot()

caused by a cross-thread race condition where the parent VM'sHandleSet

was mutated from the worker thread without holding the parent VM's lock - Fixed: memory leak and crash when terminating Workers that have a PerformanceObserver

without calling.disconnect()

β€” a reference cycle betweenPerformance

andPerformanceObserver

prevented both objects from being freed - Fixed: IPC subprocess cleanup on Windows when uv_read_start

fails after pipe open β€” the embeddedSendQueue

and its pending close task are now properly cancelled before the allocation is freed, preventing a use-after-free crash - Fixed: a crash on Linux (glibc) during Worker teardown caused by a stale .eh_frame_hdr

section reference in the stripped release binary

Timers

  • Fixed: native memory leak in setTimeout

whenclearTimeout(t)

,t.refresh()

, ort._repeat = N

is called synchronously inside the timer's own callback. The nativeTimeoutObject

struct was never freed because the post-callback cleanup only checked for the.FIRED

state, missing transitions to.CANCELLED

or.ACTIVE

that occur when the timer is cleared, refreshed, or converted to an interval during execution. - Fixed: crash when calling clearImmediate

on asetImmediate

followed by garbage collection (Bun.gc(true)

) β€” the cleared immediate's internal reference could panic during event loop cleanup if the GC had already finalized the JS wrapper - Fixed: timer.ref()

on an already-firedsetTimeout

orsetImmediate

no longer incorrectly keeps the event loop alive, which previously caused the process to hang indefinitely - Fixed: setTimeout

with an out-of-range delay no longer leaves a pending JS exception when the timeout overflow warning triggers user code that throws (e.g., a throwing getter onprocess._exiting

), which previously caused crashes in debug builds and unexpected errors in release builds

bun install

  • Fixed: bun install

no longer hangs on stalled TLS handshakes and will instead timeout. - Fixed: bunx @scope/name

no longer matches unrelated system binaries in$PATH

When running scoped packages withbunx

, the bin name was guessed by stripping the scope (e.g.@uidotsh/install

β†’install

), then searched against the full system$PATH

. If the guessed name collided with an existing system binary like/usr/bin/install

,/usr/bin/git

, or/usr/bin/find

, the system binary would be silently executed instead of the package's actual bin. - Fixed: bun install --force

now correctly replaces corrupted entries in the global store instead of silently keeping the broken version - Fixed: bun install

hanging indefinitely when a tarball download returns a 4xx/5xx HTTP error (e.g.404 Not Found

) during the install phase, particularly when resolving from an existing lockfile with an empty cache. Both the hoisted and isolated linkers now fail fast with a clear error message instead of blocking forever. (@alii) - Fixed: bun install

hanging indefinitely when a tarball integrity check fails with the isolated linker. When a tarball's SHA-512 didn't match the manifest (e.g. due to a registry redirect serving wrong content), the isolated installer would deadlock instead of reporting an error. - Fixed: a race condition in bun install

where worker threads could read lockfile data (packages

/string_bytes

) that was simultaneously being reallocated by the main thread, potentially causing crashes or failed tarball resolution when installing workspaces with relative tarball paths - Fixed: bun add

(andremove

/link

/unlink

/bunx

) crashing with a segfault when a positional argument exceeded 2048 bytes

JavaScript bundler

  • Fixed: a type confusion bug in the bundler plugin error handling that could cause a segfault when the plugin builtin threw synchronously
  • Fixed: memory leak in Bun.build()

when usingsourcemap: 'inline'

withoutoutdir

β€” the intermediate sourcemap JSON buffer was not being freed after base64-encoding - Fixed a crash in Bun.build()

andBun.Transpiler

when the``

ordefine

options contained an empty-string key (e.g.,: { "": "js", ".ts": "ts" }

) that left uninitialized memory slots, causing a segfault when the entries were later hashed or freed - Fixed: bun build --target=browser

panic involving long"browser"

field paths - Fixed: crash reading .referrer

on aResolveError

after the resolving frame had returned, particularly with non-ASCII source paths - Fixed: bundler leaving behind empty else {}

blocks after dead code elimination when not using--minify-syntax

  • Fixed: crash with malicious input in Bun.Transpiler().transformSync()

  • Fixed: crash in Bun.Transpiler

when the``

argument contains non-Latin-1 (UTF-16) characters, now properly returns aTypeError

instead of panicking

Module resolver

  • Fixed: wildcard exports

patterns (e.g."./*": "./dist/packages/*"

) failing to resolve subpaths containing@

characters, such astest-pkg/@scope/sub/index.js

. The resolver incorrectly treated the@

in the subpath as a version delimiter, causing resolution to fail even though the file existed on disk. This notably affectedember-source@6.12

and its@ember/*

,@glimmer/*

, and@simple-dom/*

subpackages. - Fixed: crash in the module resolver when a previously inaccessible directory (e.g. due to EACCES

) becomes readable again, caused by reading a cached error entry as a valid pointer - Fixed: use-after-free when resolving module specifiers containing both a query string ( ?

) and non-ASCII characters (e.g.import("./target.js?v=cafΓ©")

), which could cause crashes or corrupted resolved paths - Fixed: use-after-free crash when resolving http://

,https://

, or//

specifiers containing non-ASCII characters - Fixed: a use-after-free in the runtime auto-install path where enqueueDependencyToRoot

could read from a dangling pointer into the lockfile's dependency buffer after it was reallocated byfromNPM

  • Fixed: auto-install resolution corruption where resolving a package's subpath (e.g. nanoid/non-secure

) could fail with "Cannot find module" after auto-installing a second package, due to cached directory info pointing at a reused threadlocal buffer - Fixed: memory leak when resolving tsconfig.json

files withextends

chains β€” intermediateTSConfigJSON

structs and theirpaths

maps were never freed, causing repeated leaks on every HMR reload orFileSystemRouter.reload()

call - Fixed: memory leak in BunString__toThreadSafe

where the originalStringImpl

reference was never released when creating an isolated copy for thread safety, causing one leakedStringImpl

per call in code paths likeBun.file()

and asyncfs.write()

Dev server / HMR

  • Fixed: stale asset index in Bun's dev server causing panics or crashes when one CSS file had a syntax error and another CSS file was subsequently edited
  • Fixed: use-after-free crash in Bun's dev server ( Bun.serve

with framework) when a"use client"

directive is removed from a component that had a pending resolution failure, causing the directory watcher to read freed memory on the next file change - Fixed: double-free crash in Bun's dev server when shutting down after a directory watch resolves a previously-failing import dependency

  • Fixed: HMR bundles crashing with X is not a function

when a file has multipleimport { ... } from

statements referencing the same barrel package withsideEffects: false

(regression from barrel import optimization in v1.3.0) - Fixed: use-after-free crash in bun dev

when saving files in editors that use atomic rename (vim, emacs, IntelliJ) β€” the dev server's incremental graph stored pointers into a buffer that could be reallocated by the file watcher thread before the bundler consumed them - Fixed: bun dev

on Linux dropping file change notifications when multiple inotify events were coalesced into a single batch β€” e.g. an atomic-save creating a temp file and renaming it over the target in quick succession would silently lose the rename, leaving the file unwatched until restart - Fixed: bun --hot

on macOS stopping to detect file changes after the first atomic write (temp file + rename) when watching multiple imported modules, causing the module graph to oscillate between old and new state - Fixed: --hot

mode could print wrong source locations or silently drop errors when a file-watcher event arrived between a module's rejection and its error being reported β€” the reload now defers until the error is printed against the correct sourcemap

bun build --compile

bun build --compile

  • Fixed: ELF layout regression causing some executables on WSL1 to fail with ENOEXEC

  • Fixed: out-of-bounds write in toUTF16Alloc

when handling invalid UTF-8 input in Windowsbun build --compile

metadata flag

CSS Parser

  • Fixed: CSS background-clip

with vendor prefixes and multi-layer backgrounds could cause a double-free crash (β‰₯2 background layers) and incorrect output where prefixed clip values were silently dropped instead of being flushed as separate declarations

bun test

  • Fixed: crash in bun test --isolate

on macOS ARM64 caused by the concurrent garbage collector visiting a half-initialized global object during test isolation swaps - Fixed: bun test --isolate

andbun test --parallel

crashing with segfaults when test files load native NAPI addon whose deferred finalizers outlive the file's global object. The--parallel

coordinator also no longer silently retries crashed workers, which previously masked panics and let the run exit 0. Fatal signals (SIGSEGV, SIGABRT, etc.) now abort the entire test run with a clear error message. - Fixed: bun test --changed

silently skipping test files whose only dependency path to a changed source file went through a tsconfigpaths

alias with a bare-looking key (e.g.@/*

,~/*

,components/*

). The resolver'spackages=external

short-circuit was incorrectly marking these aliased imports as external before tsconfigpaths

could resolve them. - Fixed: test.each()

/describe.each()

table arrays could be garbage collected between the.each(arr)

call and the subsequent("name", cb)

invocation, causing callbacks to receive corrupted data or throwingExpected array, got …

errors - Fixed: crash when a custom asymmetric matcher's implementation throws an exception during .asymmetricMatch()

β€” the exception is now properly propagated to JavaScript instead of triggering an internal assertion failure - Fixed: mock.module

/vi.mock

calling the module resolver before validating the callback argument, which caused unexpected behavior

Bun Shell

This release fixes over 70 bugs with the Bun Shell.

  • Fixed: cd

in Bun's shell hanging forever when encountering errno values other thanENOTDIR

/ENOENT

/ENAMETOOLONG

(e.g.EACCES

,ELOOP

,EIO

). Now properly reports the error to stderr and exits with code 1. - Fixed: a crash in Bun's shell when cd

or.cwd()

received a path longer than 4096 bytes β€” oversized paths now correctly returnENAMETOOLONG

instead of segfaulting - Fixed: [[ -f path ]]

in Bun Shell incorrectly returned true for directories, character devices, sockets, and FIFOs instead of only regular files - Fixed: shell tilde expansion dropping path segments after command substitutions in compound words (e.g. echo ~/$(echo bin)/subdir

now correctly outputs/home/user/bin/subdir

instead of/home/user/bin

)

TypeScript types

  • Fixed: bun-types

FFI type declarations failing to compile undertsgo

(TypeScript native preview) due to duplicate computed property keys inFFITypeToArgsType

andFFITypeToReturnsType

interfaces

Windows

  • Fixed: Bun.connect()

on Windows named pipes could crash (heap-buffer-overflow under ASAN) or leak memory on connection close due to the socket handlers being incorrectly marked as server mode instead of client mode - Fixed: heap buffer overflows in Windows path normalization ( normalizePathWindows

) that could cause heap corruption when handling paths near or exceeding the 32,767 UTF-16 code unit limit. Affected code paths include UTF-8 to UTF-16 conversion, device paths (\\.\

), absolute path normalization, separator-free paths, and relative path joining withdirfd

. Now properly returnsENAMETOOLONG

instead of writing out of bounds. - Fixed: crash on Windows ( panic: integer overflow

) when tearing down hundreds of spawned child processes during process exit, caused by an unsigned integer underflow in the libuv event loop'sactive_handles

counter - Fixed: a panic on Windows when resolving error names from libuv error codes (e.g. ENOENT

,EBADF

) due to an integer overflow in internal errno translation (@dylan-conway) - Fixed: a crash on Windows ( Panic: invalid enum value

) when libuv returned unmapped error codes during file system operations likefs.readFile

CLI and runtime

Pipeline producer exit no longer clobbers downstream pager's terminal state

When piping Bun's output to tools like less

, fzf

, or fx

(e.g. bun script.js | less

), Bun would unconditionally restore its startup terminal (termios) snapshot at exit. Because termios state belongs to the underlying /dev/pts/*

device β€” not the file descriptor β€” this would overwrite the raw mode that the downstream pager had already set, leaving it unresponsive to keypresses.

bun app.js | less

bun app.js | less

The fix gates the exit-time tcsetattr

on whether Bun actually modified the terminal during its lifetime. When Bun is a pipeline producer (stdout is a pipe), fds it never touched are now left alone at exit. When stdout is a TTY (e.g. bun run vim

, crash handler, --watch

reload), the unconditional restore is preserved so the shell prompt always comes back to a usable state.

Other CLI/runtime fixes

  • Fixed: bun -p

with top-levelawait

expressions now returns the final completion value instead of the first awaited value β€” e.g.bun -p '(await 1) + 1'

now correctly prints2

instead of1

── more in #developer-tools 4 stories Β· sorted by recency
── more on @bun 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/bun-v1-3-14] indexed:0 read:56min 2026-05-13 Β· β€”