Bun v1.4.1 fixes 202 issues, addressing 236 π.
To install Bun
curl
curl -fsSL https://bun.sh/install | bashnpm
npm install -g bunpowershell
powershell -c "irm bun.sh/install.ps1|iex"scoop
scoop install bunbrew
brew tap oven-sh/bunbrew install bundocker
docker pull oven/bundocker run --rm --init --ulimit memlock=-1:-1 oven/bunTo upgrade Bun
bun upgradeNew in the runtime#
Reduced idle memory usage#
We've made JavaScriptCore delete JIT-generated code after an extended idle period, reducing memory usage for long-running processes.
RSS after 60 seconds of load followed by 3 minutes idle, on Linux x64:
| App | Bun v1.4.1 | Bun v1.4.0 | Bun v1.3.14 | Node.js v26 |
|---|---|---|---|---|
| Next.js SSR | 142 MB | 222 MB | 1,303 MB | 195 MB |
vite dev | 111 MB | 142 MB | 292 MB | 115 MB |
| Express | 53 MB | 65 MB | 76 MB | 83 MB |
| Fastify | 55 MB | 65 MB | 78 MB | 89 MB |
| Elysia | 44 MB | 46 MB | 69 MB | 96 MB |
| Hono | 34 MB | 35 MB | 53 MB | 92 MB |
Bun.serve supports HTTP/2#
Bun.serve now supports HTTP/2 on the same port as HTTP/1.1. Both protocols use the same routes and fetch handler. req.url and req.body behave identically on each.
Bun.serve({
tls: { key, cert },
http2: true,
fetch(req) {
return new Response("hi");
},
});Over TLS the protocol is negotiated with ALPN. On cleartext connections, a client that sends the HTTP/2 preface is served HTTP/2. Set http1: false to refuse HTTP/1.x clients. WebSockets and response trailers are not yet supported over HTTP/2.
Bun.write(path, response) streams to disk#
Bun.write() now streams a Response, Request, or ReadableStream body into the file instead of reading it into memory first. Writing a 128 MiB download previously added 161 MB to peak RSS. It now adds 13 MB.
await Bun.write("./big.tar.gz", await fetch(url)); // => bytes written
await Bun.write("./out.txt", readableStream); // was "[object ReadableStream]"fetch() and S3 downloads now pause when 256 KiB of the body is unread. A Response that is discarded without being read closes its connection.
WebSocket supports pause() and resume()#
Bun's WebSocket client now has pause() and resume() methods. They stop and restart reads from the underlying TCP socket.
const socket = new WebSocket("wss://example.com/feed");
socket.addEventListener("message", (event) => {
if (!file.write(event.data)) {
// the file's buffer is full: stop reading until it drains
socket.pause();
file.once("drain", () => socket.resume());
}
});The WHATWG WebSocket API cannot stop receiving messages without closing the connection. Messages that arrive faster than they are processed accumulate in memory. While a socket is paused, Bun does not read from it and the peer receives TCP backpressure.
socket.pause(); // => true
socket.isPaused; // => true
socket.resume(); // => true
socket.isPaused; // => falseMessages that were already decoded before the pause can still arrive. This is a Bun extension and is not available in browsers.
socket.bufferedAmount now reports how many bytes are still queued to send. It was previously always 0. The ws package's pause(), resume(), and bufferedAmount work too.
node:crypto supports crypto.argon2 and crypto.argon2Sync#
crypto.argon2() and crypto.argon2Sync() are now implemented. Previously, both were defined but threw ERR_CRYPTO_ARGON2_NOT_SUPPORTED when called. Feature detection therefore reported support that did not exist.
crypto.argon2Sync("argon2id", {
message: "pw",
nonce: "saltsalt",
parallelism: 1,
tagLength: 32,
memory: 8,
passes: 1,
});
// => <Buffer ...> (32 bytes)argon2d,argon2i, andargon2idare supported.crypto.argon2()runs on the thread pool with a callback.- Output matches Node byte for byte.
Up to 9x faster Buffer reads and writes#
buf.readInt32LE(), buf.writeDoubleLE(), and the rest of the Buffer read*/write* methods are inlined by the JIT into a bounds-checked load or store.
buf.writeFloatLE(1.5, 0); // 2.85ns -> 0.31ns (9.2x faster)
buf.writeUInt8(255, 0); // 2.24ns -> 0.31ns (7.2x faster)
buf.readUInt32BE(0); // 0.76ns -> 0.42ns (1.8x faster)Measured on macOS arm64 against Bun v1.4.0. The write* methods sped up the most.
readIntLE(offset, byteLength) and its variable-width siblings are inlined too when byteLength is a constant 1, 2, or 4.
Faster AsyncLocalStorage#
AsyncLocalStorage.run() is about 2x faster. An active store no longer costs an extra allocation on every await, .then(), or .finally(). An await inside run() is now as cheap as one outside it.
const als = new AsyncLocalStorage();
als.run({ requestId }, async () => {
await next(); // no per-await allocation for the store
als.getStore(); // { requestId }
});| ns/op | Node 26 | Bun, before | Bun v1.4.1 |
|---|---|---|---|
als.run(v, fn) | 410 | 28.8 | 15.9 |
3x nested run() | 2833 | 338 | 57.8 |
await under a store | 36.8 | 44.1 | 34.5 |
Promise.all of N .then() | 435 | 345 | 281 |
enterWith() called inside a timer, I/O callback, or request handler no longer leaks its store into the next unrelated callback. This matches Node.js.
Faster Bun.inspect, console.log, and util.inspect on large values#
Printing an object took time quadratic in its number of properties. It is now linear. expect() failure messages use the same code and are also faster.
Bun.inspect(objectWith16000Keys); // 140 ms -> 3.2 ms (43x faster)util.inspect now stops walking an array or typed array once it reaches maxArrayLength, instead of visiting every element and discarding the rest. Output is unchanged. REPL tab completion on big arrays gets the same speedup.
util.inspect(new Array(2e6).fill(7), { maxArrayLength: 3 }); // 2090 ms -> 42 msrequire() is faster for built-in Node.js modules#
node:fs, node:assert, node:util, node:vm, node:http and a few others load their internals on first use instead of at require() time.
| Module | Bun v1.4.1 | Bun v1.4.0 |
|---|---|---|
node:assert | 0.64 ms | 6.22 ms |
node:https | 4.77 ms | 8.14 ms |
node:http | 4.72 ms | 7.44 ms |
node:vm | 0.55 ms | 2.57 ms |
node:fs | 0.87 ms | 2.75 ms |
node:util | 1.02 ms | 2.32 ms |
node:tty | 3.25 ms | 4.35 ms |
node:worker_threads | 1.42 ms | 2.43 ms |
node:http2 | 6.87 ms | 7.58 ms |
node:child_process | 1.15 ms | 1.59 ms |
Faster first HTTPS request#
The first TLS connection in a process starts up to 3x faster.
Time from process start to the first fetch("https://β¦") response, on Linux x64:
| Root certificates | Bun v1.4.1 | Bun v1.4.0 |
|---|---|---|
| Bundled (default) | 10.3 ms | 17.6 ms |
System (--use-system-ca) | 15.3 ms | 46.3 ms |
Bun now embeds its root certificates as DER and only parses one when a certificate chain needs it.
An expired certificate in a trust store no longer hides a valid certificate for the same issuer. BoringSSL is updated to current upstream.
fetch() reuses unix socket connections#
fetch() over a unix socket keeps connections alive and reuses them, like it already does for TCP.
for (let i = 0; i < 3; i++) {
await fetch("http://localhost/x", { unix: "/tmp/app.sock" });
}
// before: 3 connections. now: 1fetch(url, { unix, tls: { ca } })uses the customca.- A relative
unixpath no longer connects to the wrong socket afterprocess.chdir().
localhost and *.localhost resolve to loopback everywhere#
fetch(), WebSocket, Bun.connect(), and bun install now resolve localhost and any *.localhost name to ::1 and 127.0.0.1 without querying the system resolver. This fixes connecting to a Bun.serve() bound to "localhost" inside a Docker container that only has IPv4.
await fetch("http://app.localhost:3000"); // works on macOS, Windows, and Linuxnode:net and node:dns are unchanged.
fetch() verifies TLS against the URL, not the Host header#
If tls.servername was not set, fetch() used a custom Host header as the TLS server name and checked the certificate against it - matching what node:https does. Bun now uses the URL's hostname, as Node's fetch and curl do. The Host header is still sent, but it no longer affects TLS.
The previous behavior was an insecure default. Applications that pass a Host header from user input to fetch(), as many proxies do, should upgrade.
// no longer fails with ERR_TLS_CERT_ALTNAME_INVALID
await fetch("https://a.example/", {
headers: { Host: "b.example" },
});To connect to an IP address and verify the certificate against a different name, set tls.servername:
await fetch("https://10.0.0.5/", {
tls: { servername: "internal.example" },
});binaryType = "blob" on server WebSockets#
ServerWebSocket and the built-in ws package now accept "blob", like the native WebSocket client.
ws.binaryType = "blob"; // binary messages arrive as BlobPreviously, a ws server socket in "arraybuffer" mode emitted a Uint8Array. It now emits an ArrayBuffer to match npm ws.
--env-file reads pipes, FIFOs and /dev/stdin#
An explicit --env-file can now read from process substitution, a named pipe, or stdin, as in Node. Secret managers such as 1Password can provide environment variables this way without writing them to disk.
bun --env-file=<(echo A=1) -e 'console.log(process.env.A)' # => 1
echo A=1 | bun --env-file=/dev/stdin app.tsReading from a FIFO blocks until a writer opens it. The default .env* lookup still skips non-regular files.
--no-ffi-cc disables cc() from bun:ffi#
This prevents untrusted JavaScript from compiling and loading C code at runtime.
bun --no-ffi-cc ./script.ts
# cc() throws ERR_FFI_CC_DISABLED--no-addons disables cc() too. Workers inherit both flags and cannot re-enable them.
Import attributes are typed on TypeScript 7.1#
With TypeScript 7.1 or later, bun-types types an import by its with { type } attribute.
import html from "./index.html" with { type: "text" }; // string, not HTMLBundle
import db from "./my.db" with { type: "sqlite" }; // DatabaseCovers text, file, md, markdown, toml, yaml, jsonc, json5, xml, sqlite, and html.
Older TypeScript versions keep the extension-based types.
Upgraded JavaScriptCore#
Bun's JavaScriptCore includes about 400 upstream WebKit commits. They include fixes for JIT miscompiles and for crashes on deeply nested destructuring patterns.
await import("./flaky.ts").catch(() => {});
await import("./flaky.ts"); // retries the load instead of rejecting with the cached errorPromise.tryfollows the updated spec.WebAssembly.Module.imports()andexports()descriptors no longer include the non-standardtypefield.
New in bun install#
Self-contained node_modules for workspace packages#
Workspace packages listed under "selfContained" in the "workspaces" object of package.json get a self-contained node_modules. This is for tools that expect a specific node_modules layout, such as Electron.
package.json
{
"workspaces": {
"packages": ["apps/*"],
"selfContained": ["apps/desktop"]
}
}Yarn's "installConfig": { "hoistingLimits": "workspaces" } in the workspace's own package.json works too.
bun install --offline#
bun install --offline makes no network requests. Every package must already be in Bun's cache. It is intended for CI jobs that restore a cache and for machines without network access.
bun install --offlineTo make it the default, set it in bunfig.toml:
bunfig.toml
[install]
offline = trueA package missing from the cache is an error that names it:
error: --offline: no cached manifest for "left-pad"
(run once online, or use --prefer-offline)bun install --prefer-offline#
By default, bun install checks for newer versions by re-fetching a package's metadata once the cached copy expires. --prefer-offline skips that check and uses the cached copy regardless of age. Packages that are not in the cache are still downloaded.
bun install --prefer-offlineTo make it the default, set it in bunfig.toml:
bunfig.toml
[install]
prefer = "offline"New in bun build#
Smaller bundles for libraries that use export * as#
Libraries like zod and Effect group their exports with export * as. When a program called z.object(), Bun 1.4.0 kept every export of that group. It built a namespace object with a getter for each one. Bun 1.4.1 compiles z.object to a direct reference to the object function, and unused exports are tree-shaken.
out.js
var User = exports_external.object({ a: exports_external.string() });
var User = object({ a: string2() });Each program below calls two or three functions from one library, built with bun build --minify:
| Package | Bun 1.4.0 | Bun 1.4.1 | Change |
|---|---|---|---|
fp-ts 2.16 | 21.8 KB | 3.2 KB | β85% |
zod 4.5 | 375.3 KB | 77.3 KB | β79% |
ox 1.7 | 15.4 KB | 4.7 KB | β70% |
effect 3.22 | 369.1 KB | 163.6 KB | β56% |
@sinclair/typebox 0.34 | 107.1 KB | 53.3 KB | β50% |
In the zod bundle, Bun 1.4.0 kept this object with 252 entries:
Bun 1.4.0 output
var exports_external = {};
__export(exports_external, {
$brand: () => $brand,
NEVER: () => NEVER,
ZodAny: () => ZodAny,
// ...249 more
});Every entry kept its export in the bundle, including ZodDiscriminatedUnion, ZodRecord, ZodTemplateLiteral, the JSON Schema generator, and 62 locale files. Bun 1.4.1 removes all of them.
The namespace objects that are still generated accept assignment, which a real module namespace does not. To make them getter-only so that assigning throws, set deprecatedNamespaceObjectSetters: false or pass --no-deprecated-namespace-object-setters. This will be the default in a future release.
Tree-shaking through dynamic import()#
bun build now removes the exports of a dynamically imported module that the importing code never reads.
main.ts
// is.ts exports isNumber, isOdd, and isEven
const { isOdd } = await import("./is");
console.log(isOdd(3));With bun build ./main.ts --splitting --outdir dist, is.ts becomes its own chunk:
dist/is-1qza7reb.js
function isNumber(n) {
return typeof n === "number";
}
function isOdd(n) {
return isNumber(n) && n % 2 === 1;
}
function isEven(n) {
return !isOdd(n);
}
export {
isEven,
isNumber,
isOdd
};If the namespace escapes (passed to a function, spread, Object.keys(ns), computed access), every export is kept.
This also works without --splitting when every use of the import() result reads a named export. const { z } = await import("zod") bundles the same code as import { z } from "zod".
bun build ./main.ts --minify --outfile=out.js# zod 4.5 377.9 KB -> 78.2 KB
# effect 3.22 248.1 KB -> 125.7 KBThe same applies to require() of an ES module. A CommonJS module keeps every export.
Fewer, smaller chunks with --splitting#
bun build --splitting now writes fewer files. Code that an entry shares with the routes it loads through import() stays in the entry's chunk. Previously it was placed in a separate chunk, which required one more request.
main.ts
import { format } from "./format";
console.log(format(1));
document.body.onclick = async () => {
// settings.ts also imports ./format
const { settings } = await import("./settings");
settings();
};bun build
bun build ./main.ts --splitting --outdir distThis previously produced 3 files. It now produces 2.
| Before | Now |
|---|---|
main.js | main.js (includes format.ts) |
settings-mn3j380d.js | settings-mn3j380d.js |
chunk-2fk8vd1p.js (format.ts) |
In a test app with one entry and 40 lazy routes:
| Before | After | |
|---|---|---|
| Output files | 219 | 151 |
| Output size | 124 KB | 75 KB |
| Modules loaded at startup | 70 | 2 |
Two related bugs are also fixed:
- An entry no longer imports chunks it uses nothing from.
- Shared chunks run in the same order as the unbundled code.
--min-chunk-size#
--min-chunk-size folds small chunks into a chunk that more entries load.
bun build ./index.tsx --splitting --min-chunk-size=16384 --outdir outawait Bun.build({
entrypoints: ["./index.tsx"],
splitting: true,
minChunkSize: 16 * 1024,
});Only chunks with no top-level side effects are folded. Lazily loaded code stays lazy, and side effects do not run earlier.
Each entry gains at most about 1.5% unused code, so a large value is safe. The default is 0, which turns it off.
bun build ./index.html --production --splitting | without | --min-chunk-size=16384 |
|---|---|---|
| Medusa admin dashboard, JS files | 349 | 245 |
| Requests per route navigation, median / max | 13 / 75 | 8 / 30 |
| 50-route React SPA, JS files | 242 | 123 |
| Requests per route navigation | 54 | 15 |
Module preloading for code-split browser builds#
With --splitting and --target browser, Bun adds <link rel="modulepreload"> for the chunks your code needs, so the browser fetches them in parallel.
bun build ./index.html --splitting --outdir distHTML entrypoints get a <link rel="modulepreload"> for every chunk their script imports:
dist/index.html
<head>
<script type="module" crossorigin src="./index-9tft256y.js"></script>
<link rel="modulepreload" crossorigin href="./index-60vtcrm3.js">
</head>A lazy import() now also preloads the chunks that its target imports. In the example below, settings-msmf5hdm.js imports two chunks. __preload adds a <link rel="modulepreload"> for each of them, so the browser fetches them in parallel with settings-msmf5hdm.js. Previously, the browser found those two chunks only after settings-msmf5hdm.js had loaded and been parsed. That took a second round trip.
dist/index-9tft256y.js
const { settings } = await import("./settings-msmf5hdm.js");
const { settings } = await (__preload("msmf5hdm"), import("./settings-msmf5hdm.js"));
// preloads index-60vtcrm3.js and index-0fxtmqn2.jsAn import() inside a lazily loaded chunk gets the same __preload. Previously, the browser discovered each chunk only after the previous one loaded. This added one round trip per level.
This is on by default. To turn it off, set modulePreload: false in Bun.build or pass --no-module-preload.
Smarter CommonJS to ESM conversion for default imports#
When you import pkg from "pkg" and pkg is CommonJS, bun build now tries to compile pkg.foo to a plain top-level variable instead of a property read on a wrapper object. It does this when it can see every export the module assigns, which covers packages like react, react-dom, and scheduler.
Previously, this was only supported for import * as pkg from 'pkg' or named imports, now it also is supported on default imports - and several bugs fixed along the way.
in.js
import React from "react";
const el = React.createElement("div");out.js
var import_react = __toESM(require_react());
var el = import_react.default.createElement("div");
var el = $createElement("div");# import React + React.createElement("div"), react@19, --production --minify
8,206 -> 1,392 bytesThe same conversion now also applies through module.exports = require("./impl") redirect files such as react-dom/index.js. Bun falls back to the wrapper when it cannot prove the conversion is safe: the module assigns module.exports dynamically, assigns both module.exports and exports.foo, reads module for anything other than module.exports, or is external.
require() is a chunk boundary with --splitting --target bun#
A require() of an ES module now gets its own chunk, as with import(). The call remains synchronous. A require() inside a function that never runs no longer loads that module at startup.
const { tool } = require("./tool.ts");
// bundled as: import.meta.require("./chunk-a1b2c3.js")A --compile --bytecode benchmark had 400 lazily required modules, and 8 of them were used at startup. Startup time went from 23.2 ms to 11.2 ms and memory use from 34.6 MB to 6.9 MB.
Pass splitRequire: false or --no-split-require to keep the old inlined output.
Smaller code-split bundles#
bun build --splitting --minify assigns each binding shared between chunks a single bundle-wide name. Chunks no longer rename imports and exports.
// before
import { Qc as ur } from "./chunk-a.js";
// after
import { a } from "./chunk-a.js";On a 100-route app with 600 shared modules, minified output went from 421 KB to 340 KB (19% smaller, and 18.7% smaller gzipped).
Nested classes and functions keep their names in bun build#
bun build no longer renames a class or function in a nested scope from a name like Abc to Abc2 when a top-level binding in the bundle shares its name. .name and constructor.name now match unbundled code, Node.js, and Rollup.
const factory = () => {
class Model {}
return Model;
};
const Model = factory();
Model.name; // was "Model2", now "Model"A nested binding is still renamed when its scope references the outer name. --minify output is unaffected.
bun build is also faster on large ESM inputs. Bundling three.js Γ100 went from 1.83s in v1.4.0 to 1.12s on Linux x64.
New in bun build --compile#
Compiled executables start faster#
We added fast paths to Bun's module loader for executables built with bun build --compile. Claude Code is compiled with Bun. Its time to the input box dropped 20%, from 397 ms to 318 ms.
Smaller bytecode#
Previously, JavaScriptCore's serialized bytecode was about 9x larger than the source code it parsed from. Packing structs, deduplicating data, and moving data reduced the multiple from 9x to 3x. This made Claude Code's install 45% smaller, from 376 MB to 207 MB.
--bytecode-depth is a new option that limits how many levels of nested functions get bytecode ahead of time. Functions past the limit compile on their first call.
bun build --compile --bytecode --bytecode-depth=1 ./app.ts --outfile appCross-compile with --bytecode#
Cross-compilation with --bytecode is now supported. Previously, this did not work when cross-compiling to or from Windows x64 on macOS or Linux.
bun build --compile --bytecode --target=bun-windows-x64 ./app.tsThe bytecode cache format is now the same on every platform, so two builds of the same input produce byte-identical executables.
Smaller text imports in bun build --compile#
Text imports in a compiled executable are now embedded once in pre-encoded form. JavaScript reads them without parsing or copying.
Previously, a 3 MB ASCII text import added 6 MB to a --bytecode binary and 12.6 MB of memory. It now adds 3 MB to the binary and about 1 MB of memory.
import readme from "./README.md" with { type: "text" };
// no longer parsed as a module or copied onto the heap at startup- Fixed:
import addon from "./x.node"threw__require is not definedin ESM bundles.
New in bun test#
bun test --isolate no longer leaks between files#
Test files that call mock(), spyOn(), mock.module(), or Bun.plugin() no longer keep their module graph alive after they finish. Memory under --isolate now stays flat as files run.
bun test --isolate # 80 files with mocks: 1591 MB -> 215 MB, 1.95s -> 1.46sprocess.env.TZ, proxy, and TLS settings no longer carry into later test files.- A file that leaves
monitorEventLoopDelay()enabled no longer crashes the run.
Other improvements#
- Improved: the release
bunbinary is about 1 MB smaller.
- Improved:
bun -euses about 4% less memory and no longer spawns an idle allocator thread.
- Improved: paused sockets on macOS no longer wake the event loop for every incoming packet.
- Improved: faster stream consumers,
ReadableStream.from(), and async-iterableResponsebodies.
- Improved:
WebSocketclients withpermessage-deflateuse about 12 KB less memory per connection.
- Improved:
Blob.text(),Response.text(), andTextDecoder.decode()throwERR_MEMORY_ALLOCATION_FAILEDwhen out of memory, like Node'sBlob.text()andResponse.text().
- Improved:
bun dedupe,bun prune --production, andbun pm licensesprint clearer output without false warnings.
- Improved:
bun build --compileexecutables ignoreNODE_COMPILE_CACHEfrom the environment.
- Improved: CSS parse errors print the offending token as written, e.g.
@corurl(x)instead ofcorx.
- Improved: hardened
Bun.S3Clientendpoint parsing to matchnew URL().
- Improved:
@types/bunaccepts everyTextDecoderencoding label the runtime supports, like"windows-1251".
Bugfixes#
Node.js compatibility improvements#
- Fixed:
node:httpservers mishandled requests with an emptyTransfer-Encodingheader, unlike Node.- Emitted a spurious
clientErrorafter serving the request, so a typical handler destroyed the connection. - Rejected the request when
Content-Lengthwas also set, instead of reading the body byContent-Length.
- Emitted a spurious
- Fixed:
node:httpresponses emitted'close'before'finish'whenres.destroy()followedres.end().
- Fixed:
node:httpsent non-ASCII header values as UTF-8 instead of latin-1 when the string came fromTextDecoder,JSON.parse(), ornormalize().
- Fixed:
http.Server.listen(port, cb)never calledcbwhen retried from anEADDRINUSEerror handler. This made vite's port auto-increment hang.
Fixed:
undici.request()rejected anode:streamReadablebody. It now streams it.Improved:
readline.createInterface()no longer loadsnode:fs,node:util, ornode:vm(58 to 41 internal modules).
- Improved:
server.listen()no longer loadsnode:clusterin the primary process.
- Fixed: several
node:http2server and client differences from Node.js.Http2SecureServerlackedcloseIdleConnections(), breaking Fastify'sforceCloseConnections: "idle"option.session.serverwasundefinedon server sessions, unlike Node.js.- Client
session.destroy(undefined, code)putcodein the GOAWAY frame, so the peer saw an error instead of a clean close. - Incoming header values with bytes that are not valid UTF-8 became
οΏ½instead of being read as latin-1 like Node.
- Fixed: several
node:http2interoperability issues that could make gRPC calls fail against@grpc/grpc-js, grpc-go, and nghttp2 peers, and that garbled non-ASCII header values sent to Node.js.session.altsvc()now passesoriginthrough unchanged, like Node.js.
- Fixed:
net.createServer(cb)did not registercbas a"connection"listener, breaking Mockttp.
- Fixed: a regression in 1.4.0 where the process exited before a
net.Socketconnected ifunref()orpause()was called first, breakingtestcontainers.
- Fixed: a paused
net.SocketorTLSSocketnever emitted'end'when the peer closed the connection, soserver.close()hung.
- Fixed:
tls.getCACertificates('system')returned each root certificate 5-6 times on Linux distros that link several CA bundle paths to one file.
- Fixed: HTTPS requests through a SOCKS5 proxy with
undicileft one proxy tunnel open per request, unlike Node.
- Fixed: a TLS 1.3
tls.connect()client whose certificate the server rejected gotECONNRESETinstead of a clean close.
- Fixed: a
node:tlswrite waiting on backpressure failed withERR_SOCKET_CLOSEDwhen the peer half-closed cleanly, which dropped AWS SDK requests.
- Fixed: recursive
fs.promises.readdir()could hang forever on a tree with directory symlink loops and an entry that fails to open.
- Fixed: async
node:fscalls given aBufferpath could leak theBuffer
- Fixed: recursive
fs.readdirSync()andfs.promises.readdir()threwENOENTwhen another process removed a subdirectory during the walk.
- Fixed:
fs.truncateSync(path, undefined)andfs.ftruncateSync(fd, undefined)threw instead of truncating to 0.
- Fixed:
fs.ReadStreamandfs.WriteStreamthrew afterObject.freeze(require("node:fs")).
- Fixed:
node:fscallback APIs threwENAMETOOLONGsynchronously for a path over the OS length limit, instead of passing it to the callback.
- Fixed: a regression in 1.4.0 where
fs.rmdir(path, { recursive: true })threw instead of removing the directory, impacting packages like@prisma/client
- Fixed: two argument-handling bugs in
crypto.sign()andcrypto.verify().crypto.sign(null, data, ecKey)threwNO_DEFAULT_DIGESTinstead of defaulting to SHA-256.- Valid
saltLengthorpaddingvalues were rejected when the number was stored as a double, such as one fromJSON.parse().
- Fixed: reading
X509Certificate.publicKeya second time crashed on a certificate whose key could not be decoded.
- Fixed:
setTimeout[util.promisify.custom]was missing untilnode:utilwas loaded.
- Fixed:
util.formatWithOptions()accepted a function as options. It now throws, like Node.
- Fixed:
util.parseEnv()andprocess.loadEnvFile()stored numeric keys like0=zeroso thatresult[0]wasundefined.
- Fixed:
assertfailure diffs garbled non-ASCII text, printingοΏ½for latin-1 characters or lists of numbers for emoji and CJK.
- Fixed: a regression in 1.4.0 where
assert.deepStrictEqual()threw for objects with different prototypes, likeObject.create({ x: 1 })and{}.
- Fixed:
dns.lookup()on macOS failed for some hostnames.- A regression in 1.4.0: hostnames served by a VPN, split-DNS, or
/etc/resolverreturnedENOTFOUNDinstead of resolving. - Names that are not valid hostnames, such as names containing a space, could wait for a 30 second timeout instead of failing with
ENOTFOUND.
- A regression in 1.4.0: hostnames served by a VPN, split-DNS, or
- Fixed:
dns.resolve(host, "NAPTR")threwERR_INVALID_ARG_VALUEinstead of running a NAPTR query.
- Fixed:
vitest --coverageandc8could hit "Maximum call stack size exceeded" mergingnode:inspectorcoverage from modules withexport functiondeclarations.
- Fixed: the
pprofpackage failed onpprof.time.profile()with an undefinedCpuProfiler::StartProfilingsymbol.
- Fixed: when a child process exited before reading all of its stdin, the
EPIPEerror reportedsyscall: "send"instead of"write"like Node.
- Fixed:
process.kill(process.pid, "SIGABRT")printed a Bun crash report instead of exiting silently.
- Fixed: when memory ran out,
buffer.toString()andStringDecoderthrewERR_STRING_TOO_LONGinstead of Node'sERR_MEMORY_ALLOCATION_FAILED.
- Fixed: a regression in 1.4.0 where
new Worker()fromnode:worker_threadsthrewport.on is not a functionwhen a library like happy-dom replacedglobalThis.MessagePort.
- Fixed: an overridden
Module._resolveFilenamegot wrongparent,isMain, andoptionsarguments, breaking proxyquire-style tools.
- Fixed: after an
importof a builtin likehttps,require.cacheexposed its frozen module. This crashed dd-trace with OpenTelemetry.
- Fixed: several rare N-API addon crashes in finalizers and on
process.exit().- On the main thread,
process.exit()previously ran addon finalizers and cleanup hooks, and these could crash. It now skips them, as Node does. process.exit()with calls still queued on a threadsafe function no longer passes a nullenvto the addon's callback.- A finalizer could still run after
napi_delete_referenceand crash on freed memory.
- On the main thread,
- Fixed:
better-sqlite3@13aborted the process with aNAPI FATAL ERRORon load.
- Fixed: transferring an N-API external buffer to a Worker could free memory in use.
- Fixed: on macOS,
node:fscalls such asopenandreaddircould throwEINTR: interrupted system callwhen a signal arrived mid-syscall, instead of retrying like Node.js.
Bun APIs#
- Fixed: two
server.upgrade()bugs inside aBun.serverequest handler.- After an
await, it closedConnection: closeand HTTP/1.0 sockets and leaked the WebSocket. server.upgrade()andws.close()ran queued microtasks in the middle of the handler.
- After an
- Fixed: a
ServerWebSocketstalled forever when a largesend()insidedrainhit backpressure.
- Fixed:
Bun.serve()with HTML routes crashed in two cases
- Fixed: when a
Bun.servehandler returned a promise that never settled, an aborted request stayed inserver.pendingRequestsuntil garbage collection.
- Fixed: when an async
Bun.servehandler returned a streamingResponseafter the client disconnected, the stream was not cancelled.
- Fixed:
Bun.servecould crash when the client disconnected while the handler was still running and its promise settled later.
- Fixed:
Bun.servemishandled responses whose body stream failed.- A body stream that errored before its first byte was sent as a complete, empty response.
- In rare cases, a handler that returned a proxied
fetch()response hung instead of runningerror().
- Fixed:
Bun.serveaccepted malformedRangeheaders with signed or_-separated positions.
- Fixed:
Bun.serve({ app })accepted wrong-typed options instead of throwingERR_INVALID_ARG_TYPE.
- Fixed:
Bun.serveandBun.listenthrew a misleadingENOENTfor a malformed hostname.
- Fixed:
Bun.file(p).slice(a, b)sent the whole file when its unread.stream()was passed to aResponseorbytes(). It also sent the wrong bytes when used as aBun.serveroute.
- Fixed:
blob.writer()andwriter.start()silently ignored an invalidpathorfdoption.
- Fixed:
Bun.sha()silently ignored an invalidoutputargument instead of throwing.
- Fixed:
Bun.CSRFthrewTypeErrorinstead ofRangeErrorfor out-of-rangeexpiresInandmaxAge.
- Fixed:
Bun.YAML.stringifywith an indent put boxedNumberandBooleanvalues on the line after their key.
- Fixed: JSON5 errors for bad
\xand\uescapes reported the wrong column.
- Fixed: on the Chrome backend, closing a
Bun.WebViewcreated with aurloption raised an uncatchableWebView closedrejection.
- Fixed:
Bun.WebView(WebKit backend) hung onnavigate()after 64 views in one process.
- Fixed:
Bun.Imageon macOS turned undecodable HEIC files into black images and rejected 10-bit HEIC.
- Fixed: an intermittent JIT bug, mostly seen on Windows, when optimized code passed an
ArrayBufferto abun:ffipointer argument.
- Fixed:
toBuffer()andtoArrayBuffer()frombun:ffireturned argument errors instead of throwing them.
- Fixed: on macOS and Linux,
Bun.servebuffered a FIFO, socket, or deviceBun.file()response body without limit when the client wasn't reading, sometimes crashing if the client then disconnected.
- Fixed: with
--splitting,Bun.servereturned 404 for assets imported only by a module that an HTML import loads via dynamicimport(), and for the lazy chunks themselves when there were two or more HTML imports.
Web APIs#
- Fixed:
fetch()with atimeoutof 4 seconds or less aborted requests early withTimeoutError, a regression in 1.4.0.
- Fixed:
fetch()withtls.checkServerIdentityopened a new TLS connection for every request, a regression in 1.4.0.
- Fixed:
fetch()gave 204, 205, 304, and HEAD responses a non-nullbody, soclone()threw after reading.
- Fixed:
new Response(asyncGenerator)appended the generator'sreturnvalue to the body.
- Fixed:
Headersrejected non-ASCII latin-1 values fromJSON.parseor.normalize().
- Fixed: a streamed
fetch()response could lose the end of its body withECONNRESETwhen the server closed while the client was still uploading, a regression in 1.4.0.
- Fixed:
new WebSocket("ws://[::1]:PORT")sent aHostheader without the port.
- Fixed:
wshandleUpgrade()threw aTypeErrorwhen called after anawait, a regression in 1.4.0.
- Fixed: a rare crash when a
wss://WebSocket through a proxy closed mid-write.
- Fixed: in rare cases,
pipeTo()and BYOBread()threw synchronously instead of returning a rejected promise.
- Fixed:
TextEncoderStreamcrashed on a huge chunk instead of throwing an out-of-memory error.
- Fixed:
new TextDecoder("utf-8", { ignoreBOM: 1 })threw instead of coercing to a boolean.
- Fixed:
crypto.subtleRSA-PSS acceptedsaltLengthvalues near 2^32 instead of rejecting them withOperationError.
- Fixed:
JSON.stringify(performance.mark())omitteddetailunlessnode:perf_hookswas loaded.
- Fixed:
clearTimeout()did nothing when the timer id was an integer stored as a float, such as one read from aFloat64Arrayor returned byMath.sqrt().
Runtime#
- Fixed:
import()of a file mixingimportandmodule.exportsthrew a confusingSyntaxError.
- Fixed:
require()of a.tsfile, or running it directly, failed with "Cannot use import statement" when a type-only import was in the same file asmodule.exports.
- Fixed:
import()crashed after a user-defined getter onprocessorBunloaded an ES module withrequire()and then threw.
- Fixed: a Worker leaked 12 KB on exit when its entry point used a package.json
importsorexportsmap.
- Fixed: reading
error.stackleaked memory when runningbun build --sourcemapoutput that has an external.mapfile.
- Fixed: memory leaks of strings in module loading, HMR, and several native APIs.
- Fixed: in rare cases,
structuredClone()andpostMessage()could return the wrong string when a getter changed the object while it was being cloned.
- Fixed: a crash when a Worker used an object URL for a named
Filemade in another Worker, or when several Workers got the sameBroadcastChannelmessage with objects.
- Fixed: a crash when a resizable
ArrayBuffershrank duringBun.zstdCompress()or anfscall.
- Fixed: in rare cases, a string of 1 to 3 characters could load with the wrong value from the bytecode cache.
- Fixed: printing an
AggregateErrorwithout anerrorsproperty crashed.
- Fixed: an error thrown by a
mock.moduleexport getter, or by a Proxy trap ortoJSONduringBun.inspect, was silently ignored.
- Fixed: a possible crash when a system call returned an errno Bun did not know, such as one from a FUSE filesystem. Bun reports
EUNKNOWNinstead.
- Fixed: an unparseable
LANG,LC_ALL, orLC_MESSAGESvalue crashedDateandIntlcalls.
- Fixed:
--inspectcrashed whenNODE_ENVcontained characters like a stray\r.
- Fixed: piped stdout got ANSI color codes when stderr was a TTY, e.g.
bun test | pbcopy.
- Fixed:
process.versions.icuandprocess.versions.unicodereported the wrong ICU version on macOS.
- Fixed: the FreeBSD binary failed to load on FreeBSD 15 with a missing
libutil.so.9.
- Fixed:
bun --watchcrashed on a forced reload when colors were enabled.
- Fixed: on Linux, a
bun --watchreload could in rare cases abort the process if another thread was starting at that moment.
- Fixed:
bun -pprintedundefinedwhenbunfig.tomlsethoistPatternorpublicHoistPattern.
- Fixed:
bun completionsandbun upgradecrashed on macOS and Linux when$SHELLwas PowerShell.
- Fixed: the REPL highlighter did not color numeric separators like
1_000or BigIntnsuffixes.
- Fixed: the REPL froze when typing
.after a largeBufferor typed array.
- Fixed:
bun replleft stale rows behind when input wrapped past the terminal width.
- Fixed: a 1.4.0 regression where
join(),toString(),String(arr), orarr | 0on an array that contains itself threwRangeError: Maximum call stack size exceededinstead of treating the cycle as empty, which broke three.jsWebGPURenderer.
- Fixed: a crash when two signals with
process.on("SIG...")listeners were delivered on different threads at the same time, most often on macOS while aBun.spawn()was in progress.
bun install#
- Fixed: a no-op
bun installwith the isolated linker took seconds instead of milliseconds in monorepos where a shared dependency declared a peer that no parent package provided (12.9s β 23ms in one 16-workspace repo).
- Fixed:
bun installhung on "Resolving dependencies" when a transitive peer dependency's manifest was cached from a different registry URL.
- Fixed: when the connection dropped partway through a tarball or manifest download,
bun installprinted a misleading error instead of retrying.
- Fixed:
bun installruns that cached the same package manifest at the same moment could lose the cache entry, mostly on Windows.
- Fixed:
bun installsilently skipped GitHub dependencies migrated frompackage-lock.json.
- Fixed: migrating a pnpm project could corrupt
package.json.bun addandbun update -icrashed or wrote invalid content topackage.jsonwhenpnpm-lock.yamlandpnpm-workspace.yamlwere still present.bun installcould write corruptedoverrides,catalog, andpatchedDependenciesentries whenpnpm-workspace.yamlused quoted or multi-line values.
- Fixed:
bun installpanicked when a dependency's package.json declaredpatchedDependencies.
- Fixed:
bun installfailed with "unsafe folder path" when afile:package had acatalog:peer on anotherfile:dependency with an absolute path.
- Fixed:
bun installpanicked when a project path was too long to link a package's bins.
- Fixed: in rare cases, auto-install could crash when a package lookup returned 404 while other modules were loading.
- Fixed: Ctrl-C during
bun installwith git dependencies signaledgitinstead of stopping Bun.
- Fixed:
bun installprinted an EISDIR error when.envwas a directory, and hung when it was a FIFO.
- Fixed:
bun addof a new git commit for an existing dependency duplicated its package.json key.
- Fixed:
bun add github:user/repowrote the dependency out of alphabetical order inbun.lock.
- Fixed:
bun installwrote an empty range tobun.lockfor an 8-byte range ending in a non-ASCII character, such as1.0.0-Γ©.
- Fixed:
bun update --global --recursiveerrored instead of ignoring--recursiveas Bun 1.3 did.
- Fixed:
bun prunerefused to run afternode_moduleswas deleted and reinstalled with a different linker in a workspace.
- Fixed:
bun pm lslisted a package twice when the root declared it twice, such as a workspace also listed indevDependencies.
- Fixed:
bun pm difffailed with "invalid archive entry size" on tarball files over 64 MiB.
- Fixed: depending on a workspace via
"pkg": "*"when that workspace had noversion(or a prerelease version) made everybun installre-resolve it, andbun prune/bun dedupefailed with "bun.lock does not match package.json".
- Fixed:
bun install --backend=copyfiletruncated a package's files and its global cache entry to 0 bytes when thenode_modulesdestination was already a hardlink to that cache file from an earlier install.
- Fixed:
bun pm pkg set,bun pm version,bun pack, andbun publishrewrote power-of-ten integers from10000to1000000000in package.json in exponent form like1e4.
bun run and bun init#
- Fixed:
bun run --filter,--workspaces,--paralleland--sequentialignored an auto-discoveredbunfig.tomlunless--configwas passed.
- Fixed:
bun runcould reuse stale cached output for a file of 4 KiB or more after a bunfig[define]or--dropvalue changed.
- Fixed:
bun initcrashed when an existing package.json had adevDependenciesorpeerDependenciesvalue that was not an object, likenull.
- Fixed: pressing Ctrl-D at a
bun inittext prompt printed an internal error.
bun build#
- Fixed: with several entry points and no
--splitting,Bun.buildcould crash or print the wrong text for a constant-folded string in a shared module.
- Fixed: in rare cases
bun buildgave a nested variable the same name as a top-level function, so the output threw "is not a function".
- Fixed: a 1.4.0 regression where an
onResolveplugin returningundefinedcould makeBun.build()drop code from asideEffects: falsepackage, causing aReferenceError.
- Fixed: with
--format=esm, an entry point that re-exportedfoofrom a CommonJS module could clash with anotherexport_fooin the bundle.
- Fixed:
bun buildreturned all ofmodule.exportsfor a default import of an__esModuleCommonJS module from a.jsor.tsfile, unlikebun run.
- Fixed:
bun buildemitted an empty file for an entry point that only didmodule.exports = require(...).
- Fixed: several
bun buildbugs in the barrel file optimization forsideEffectspackages.- A module listed in a package's
sideEffectsarray was dropped when it was only reachable through a re-export barrel. Bun.buildreported an unresolved re-export twice and ranonResolveplugins more than once for it insideEffects: falsebarrels.- A
sideEffects: falseentry point made only of re-exports emitted anexport { a }with no declaration.
- A module listed in a package's
- Fixed: a 1.4.0 regression where unused classes with a computed key naming a constant, like
[TypeId], were not tree-shaken, bloating Effect bundles.
- Fixed: with
--splitting,bun buildcould fail with "Multiple files share the same output path" when tree shaking removed all code from two imported files.
- Fixed:
bun buildcrashed when every entry point was disabled or marked external.
- Fixed:
bun build node:fsand other builtin entry points failed with "File not found" instead of saying builtins cannot be entry points.
- Fixed:
events.once()threw aReferenceErrorinbun build --target=browserbundles.
- Fixed:
import "./foo.cjs"did not findfoo.cts, and an exactexportsorimportstarget ending in.jsdid not fall back to the.tsfile.
- Fixed: several bugs with macros in
bun buildandBun.build().- Macros ignored
--define,--loader, and tsconfig settings while bundling. - A macro that awaited
crypto.subtle.digest()hung the build. - A macro returning
Response.json()was inlined as a base64data:URL, not an object.
- Macros ignored
- Fixed:
reactCompileradded an unusedreact/compiler-runtimeimport to components with no memoization.
- Fixed: Bun crashed on the first JSX element when a tsconfig
jsxFactoryorjsxFragmentFactoryhad no identifier in it, like""or".".
- Fixed: with syntax minification, the transpiler mishandled declarations that read members of the same object.
var a = o.x, b = o.ybecame one destructure even whenocould change between the reads, like a global with a getter.var n = n.next, n = n.nextread both members from the originaln.bun build --minifyemitted invalid code forusing a = obj.x, b = obj.yinside a function.
- Fixed:
Bun.Transpiler#scanImportsgarbled non-ASCII import specifiers.
- Fixed:
--metafile,bun audit --json, andbun pm view --jsonwrote invalid JSON when a string held malformed UTF-8 or a lone surrogate.
- Fixed:
bun buildon Windows wrote backslash-separated asset paths and HTML manifest entries.
- Fixed: dev server
<img>URLs in HTML imports returned 404 after editing the HTML file.
- Fixed: dev server crashed when editing a file imported from outside the project root.
- Fixed:
bun index.htmlcrashed on an anonymousexport default class extends React.Component {}.
- Fixed:
bun buildwithout--splittingwrote only one output file when several entry points imported each other. When one entry point imported another, modules in its output could also run in the wrong order.
- Fixed: with
--splitting,await import()of a CommonJS module in another chunk (e.g.react-dom/client) resolved to{ default }only, so named exports likecreateRootwereundefined.
- Fixed:
bun buildproduced broken output forimport * as nsof a CommonJS module that assignsexports.x = ...:ns.method()calls, andexports.fn()calls inside the module itself, droppedthis, so a method that readthis._helperthrewundefined is not an object(e.g.stack-trace).- Modules that also read
module.constructor,module.hot, ormodule.pathsthrewReferenceError: module_lib is not defined(e.g.pirates). - Modules that declared a
varwith the same name as one of their top-level functions threwSyntaxError: Cannot declare a var variable that shadows a let/const/class variablewhen the ESM bundle loaded.
- Fixed:
bun buildresolvedns[Enum.member]on animport * as nsnamespace to the wrong export when theconst enummember's value was a concatenated string like"a" + "b"(it read exportainstead ofab).
- Fixed:
bun buildtree-shaking removed an unused destructuring declaration likeconst { x } = objeven when the destructuring could run a getter or iterator with side effects.
- Fixed:
Bun.buildignored thepathanonResolveplugin returned withexternal: true, so a plugin could not rewrite an external import to a different path or URL.
bun build --compile#
- Fixed:
bun build --compilebinaries for darwin-arm64 in rare cases failedcodesign -vand were killed on macOS 27.
- Fixed: UPX-compressed
bun build --compileexecutables failed on Linux withInvalid character: '\0'.
- Fixed: a 1.4.0 regression where
bun build --compilefailed withEACCESwhen run from a WSL2/mnt/cpath.
- Fixed:
bun build --compilefor macOS panicked instead of erroring when the executable exceeded 4 GiB.
- Fixed: a 1.4.0 regression where
Bun.build({ compile: true })with nooutfileoroutdirwrote the executable next to the entrypoint, not the working directory.
- Fixed:
new Worker(new URL("./worker.ts", import.meta.url))could not find the worker inbun build --compileexecutables.
- Fixed:
Bun.Globscan()threwENOENTon embedded directories in compiled executables.
- Fixed:
bun build --compileexecutables ran with the JIT and GC throttled when their own arguments included-e,-p,--evalor--print.
- Fixed:
bun build --bytecodeintermittently aborted on large builds.
- Fixed:
--compile --bytecodechunks with non-ASCII text silently skipped their bytecode cache.
- Fixed: in a
--compile --splittingexecutable,import()of the entry point from another chunk failed withCannot find module '/$bunfs/root/<entry>.js'. The entry point's external source map is now written to<outfile>.map.
- Fixed:
bun build --compiletargeting Linux with more than 4 GiB of embedded files produced an executable that failed at startup withModule not found ''. The build now errors instead.
JavaScript minifier#
- Fixed: in rare cases
Bun.build({ minify: true })produced different identifier names across identical builds of a project withsideEffects: falsebarrel files.
CSS Parser#
- Fixed:
bun buildwarned on valid::details-content,::picker(),::checkmarkand::picker-iconselectors.
- Fixed: CSS modules did not scope
view-transition-nameand::view-transition-group()names.
bun test#
- Fixed: in rare cases,
bun test --parallelhung forever when a worker process exited before it finished starting up.
- Fixed:
bun test --parallel --coverageunder-reported function coverage for a file when different workers ran different functions in it.
- Fixed: on Windows,
bun test --parallelor--isolatecould crash when more than one test file usedBun.WebView.
- Fixed:
Bun.spawnandchild_processwith piped stdio failed withEBADFinsidebun teston macOS in repos with thousands of directories.
- Fixed: under
jest.useFakeTimers({ now })andsetSystemTime(),performance.timeOriginstayed on the real clock, sotimeOrigin + performance.now()did not matchDate.now().
- Fixed:
mock.module()silently swallowed errors thrown by aBun.pluginonResolvehook.
- Fixed:
mockResolvedValue()andmockResolvedValueOnce()swallowed the error when reading the value'sconstructorproperty threw.
- Fixed:
this.utils.printReceived()in anexpect.extendmatcher crashed when a custom inspect threw.
- Fixed: when a
Set,Map,WeakSet, orWeakMaphad a non-numeric ownsizeproperty, a failingtoEqual()printed a bareTypeErrorinstead of the diff.
- Fixed:
expect(x).toBeWithin(a)with one argument crashedbun testinstead of failing.
- Fixed: when the
toSatisfy()predicate threw,bun testwrapped the error in anAggregateErrorthat could crashconsole.log. It now rethrows the predicate's error.
- Fixed:
toMatchObject()andBun.deepMatch()crashed on objects nested thousands of levels deep instead of throwingRangeError.
- Fixed: JUnit reports and GitHub Actions annotations crashed when an error's stack included a long
data:URL module or a long//# sourceURL=name.
Bun Shell#
- Fixed: Bun Shell split
export NAME=$(cmd)output on whitespace instead of keeping one value.
- Fixed: a Bun Shell pipeline hung forever when it could not create its pipes because the process was out of file descriptors (
EMFILE).
- Fixed: Bun Shell
lserrored on a dangling symlink operand, andls file/xprinted the path instead of a "Not a directory" error.
- Fixed: after
await $`cmd > ${buf}`, the ArrayBuffer could stay pinned if the child's stdout closed after it exited, for example when a grandchild held it open.
SQL / SQLite / S3 clients#
- Fixed:
Bun.sqldecoded Postgresfloat8andfloat4Infinityvalues asNaNin text-format results, such as.simple()andsql.unsafe()without parameters.
- Fixed:
Bun.sqlreturnedInvalid Datein.simple()queries and arrays for Postgrestimestamptzvalues with a seconds offset, like-04:56:02for pre-1900 dates.
- Fixed:
Bun.sqlcould crash whenclose(),ref(), orunref()ran after the server had already dropped the connection.
- Fixed:
sql.close()firedonclosefor pool connections that never connected.
- Fixed:
Bun.sqlstoredDatevalues in MySQLTIMESTAMPcolumns at the wrong instant when the session time zone was not UTC.
- Fixed: in
bun:sqlite,db.prepare()with params crashed on SQL that held no statement (only whitespace, comments, or;) instead of throwingInvalid SQL statement.
- Fixed: a streaming S3 upload from a
ReadableStreamcould fail if garbage collection ran while the upload waited for a part to finish.- The upload could crash or hang.
- A later error from the source stream was lost.
TypeScript types#
- Fixed:
@types/bunbrokeEvent.composedPath()types whenlib: ["dom"]was enabled.
- Fixed: with
@types/node@24installed,process.off()andprocess.removeListener()rejected every event name exceptmemoryPressure.
- Fixed: TypeScript rejected
fetch(url, { protocol: "h3" }). Bun supports it at runtime.
- Fixed: TypeScript parse errors on
a ? (b) : c => d,export typefollowed by a newline, andimport type from.
Windows#
- Fixed: on Windows,
bun installworkspace pattern errors and some other error messages saidNOENTinstead ofENOENT.
- Fixed: on Windows,
Readable.fromWeb(Bun.file(path).stream())could crash while reading a file larger than the stream buffer.
- Fixed: on Windows,
fs.copyFilereported success when the copy failed with an error code Bun did not recognize, such as a missing network share.
- Fixed: on Windows,
process.chdir()during a recursivefs.rmcould close an unrelated handle.
- Fixed: on Windows,
child_process.spawn()with extra stdio pipes could close unrelated handles.
- Fixed: on Windows, Bun leaked memory when connecting to a named pipe failed.
- Fixed: on Windows, a named-pipe socket could crash if its
dataorendhandler closed it and then ran the event loop, asexpect().resolvesdoes.
- Fixed: on Windows, closing
process.stdinorBun.stdin.stream()while a console read was pending could corrupt memory and cause a later crash.
- Fixed: on Windows,
child_process,Bun.spawn, andBun.whichcould not find.comexecutables likechcp.child_processregressed in 1.4.0.
- Fixed:
bun runexited as soon as Ctrl+C was pressed, before the script finished handling it. This affected Windows and--shell=bun.
- Fixed: on Windows arm64, in rare cases a process that used WebAssembly could hang on exit.
- Fixed: on Windows Server 2019, the crash report said "CPU lacks AVX support" and omitted the
CPU:line on CPUs that do support AVX.


