Bun

Bun v1.2.23


Jarred Sumner · September 28, 2025

This release fixes 119 issues (addressing 412 👍).

To install Bun

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

To upgrade Bun

bun upgrade

pnpm-lock.yaml in bun install

bun install now automatically migrates pnpm-lock.yaml and pnpm-workspace.yaml files to a bun.lock, preserving resolved dependency versions. This includes support for pnpm workspaces and catalog: dependencies.

Switch from pnpm install to bun install with a single command:

# In a pnpm project
bun install

bun install is designed to work with existing Node.js projects. That means you can leverage bun install's incredible performance while still using Node.js as your runtime.

If your project uses pnpm-workspace.yaml, your package.json is updated to include "workspaces": ["<each package-name>"].

Thanks to @dylan-conway for implementing this!

Filter optional dependencies with --cpu and --os flags

You can now control which platform-specific optionalDependencies are installed using the new --cpu and --os flags in bun install. This is useful when you're installing dependencies for a different target environment, such as in a Docker container or a CI/CD pipeline.

You can provide multiple values to install dependencies for several targets at once.

# Install optional dependencies for a Linux ARM64 target
bun install --os linux --cpu arm64

# Install for both macOS and Linux on x64
bun install --os darwin --os linux --cpu x64

# Install for all supported platforms
bun install --os '*' --cpu '*'

Bun's Redis client now supports Pub/Sub

Bun's built-in RedisClient now supports the Publish/Subscribe (Pub/Sub) messaging pattern. You can use the new .subscribe() method to listen for messages on specific channels and the .publish() method to send messages.

This enables real-time, event-driven communication patterns directly within your Bun applications.

subscriber.ts
import { RedisClient } from "bun";

const subscriber = new RedisClient("redis://localhost:6379");
await subscriber.connect();

await subscriber.subscribe("my-channel", (message, channel) => {
  console.log(`Received message: "${message}" from channel: "${channel}"`);
  // Received message: "Hello from Bun!" from channel: "my-channel"
});
publisher.ts
import { RedisClient } from "bun";

const publisher = new RedisClient("redis://localhost:6379");
await publisher.connect();

// After a short delay to ensure the subscriber is ready
setTimeout(() => {
  publisher.publish("my-channel", "Hello from Bun!");
}, 100);

Thanks to @markovejnovic for the contribution

Concurrent bun test

bun test now supports running multiple async tests concurrently within the same file using test.concurrent. This can significantly speed up test suites that are I/O-bound, such as those making network requests or interacting with a database.

concurrent.test.ts
import { test, expect } from "bun:test";

// These three tests will run in parallel.
// The total execution time will be ~1 second, not 3 seconds.
test.concurrent("sends a request to server 1", async () => {
  const response = await fetch("https://example.com/server-1");
  expect(response.status).toBe(200);
});

test.concurrent("sends a request to server 2", async () => {
  const response = await fetch("https://example.com/server-2");
  expect(response.status).toBe(200);
});

// Chain with .each, .only, or other modifiers:
test.concurrent.each([
  "https://example.com/server-4",
  "https://example.com/server-5",
  "https://example.com/server-6",
])("sends a request to server %s", async (url) => {
  const response = await fetch(url);
  expect(response.status).toBe(200);
});

Run groups of tests concurrently with describe.concurrent.

describe.concurrent.test.ts
import { describe, test, expect } from "bun:test";

describe.concurrent("server tests", () => {
  test("sends a request to server 1", async () => {
    const response = await fetch("https://example.com/server-1");
    expect(response.status).toBe(200);
  });
});

test("serial test", () => {
  expect(1 + 1).toBe(2);
});

By default, a maximum of 20 tests will run concurrently. You can change this with the --max-concurrency flag.

concurrentTestGlob to make specific files run concurrently

To make specific files run concurrently, you can use the concurrentTestGlob option in bunfig.toml.

bunfig.toml
[test]
concurrentTestGlob = "**/integration/**/*.test.ts"

# You can also provide an array of patterns.
# concurrentTestGlob = [
#   "**/integration/**/*.test.ts",
#   "**/*-concurrent.test.ts",
# ]

When using concurrentTestGlob, all tests in the files matching the glob will run concurrently.

test.serial to make specific tests sequential

When you use describe.concurrent, --concurrent, or concurrentTestGlob, you might still want to leave some tests sequential. You can do this by using the new test.serial modifier.

test.serial.test.ts
import { test, expect } from "bun:test";

describe.concurrent("concurrent tests", () => {
  test("async test", async () => {
    await fetch("https://example.com/server-1");
    expect(1 + 1).toBe(2);
  });

  test("async test #2", async () => {
    await fetch("https://example.com/server-2");
    expect(1 + 1).toBe(2);
  });

  test.serial("serial test", () => {
    expect(1 + 1).toBe(2);
  });
});

Run tests in a random order with --randomize

Concurrent tests sometimes expose unexpected test dependencies on execution order or shared state. You can use the --randomize flag to run tests in a random order to make it easier to find these dependencies.

When you use --randomize, Bun will output the seed for that specific run. To reproduce the exact same test order for debugging, you can use the --seed flag with the printed value. Using --seed automatically enables randomization.

# Run tests in a random order
bun test --randomize

# The seed is printed in the test summary
# ... test output ...
#  --seed=12345
# 2 pass
# 8 fail
# Ran 10 tests across 2 files. [50.00ms]

# Reproduce the same run order using the seed
bun test --seed 12345

bun test now supports chaining qualifiers

You can now chain qualifiers like .failing, .skip, .only, and .each on test and describe. Previously, this would result in an error.

import { test, expect } from "bun:test";

// This test is expected to fail, and it runs for each item in the array.
test.failing.each([1, 2, 3])("each %i", (i) => {
  if (i > 0) {
    throw new Error("This test is expected to fail.");
  }
});

Stricter bun test in CI environments

To prevent accidental commits, bun test will now throw an error in CI environments (where CI=true) in two new scenarios:

  • If a test file contains test.only().
  • If a snapshot test (.toMatchSnapshot() or .toMatchInlineSnapshot()) tries to create a new snapshot without the --update-snapshots flag.

This helps prevent temporarily focused tests or unintentional snapshot changes from being merged. To disable this behavior, you can set the environment variable CI=false.

Test execution order improvements

The test runner's execution logic has been rewritten for improved reliability and predictability. This resolves a large number of issues where describe blocks and hooks (beforeAll, afterAll, etc.) would execute in a slightly unexpected order. The new behavior is more consistent with test runners like Vitest.

Concurrent test limitations

  • expect.assertions() and expect.hasAssertions() are not supported when using test.concurrent or describe.concurrent.
  • toMatchSnapshot() is not supported, but toMatchInlineSnapshot() is.
  • beforeAll and afterAll hooks are not executed concurrently.

Thanks to @pfgithub for all of these big improvements to bun test!

bun feedback

Node.js compatibility improvements

node:http

http.createServer now supports the CONNECT method. This allows for creating HTTP proxies with Bun.

node:dns

dns.resolve's callback now matches Node.js by no longer passing an extra hostname argument. dns.promises.resolve now correctly returns an array of strings instead of objects for A/AAAA records.

node:worker_threads

A bug where MessagePort communication would fail after being transferred to a Worker when using port.on('message', ...) or port.addEventListener('message', ...). This was caused by a different between Web Workers and worker_threads.

node:crypto

A hypothetical crash in crypto.createSign().sign() when using an Elliptic Curve (EC) key in JWK format with dsaEncoding: 'ieee-p1363' has been fixed.

node:http2

A memory leak when closing sockets has been fixed.

node:net

A handle leak in net.connect() that caused memory usage to grow when making many connections has been resolved.

node:tty

On Windows, TTY raw mode (process.stdin.setRawMode(true)) now correctly handles terminal VT control sequences, improving compatibility with Node.js and enabling features like bracketed paste mode.

Use system's trusted certificates with --use-system-ca

Bun can now be configured to use the operating system's trusted root certificates for establishing TLS connections, in addition to the built-in Mozilla CA store. This is useful in corporate environments with custom Certificate Authorities (CAs) or for trusting locally installed self-signed certificates.

To enable this functionality, either use the new --use-system-ca command-line flag or set the NODE_USE_SYSTEM_CA=1 environment variable, which aligns Bun with Node.js's behavior.

# This command will use the OS's trusted CAs to validate the certificate
# for "internal.service.corp".
bun run --use-system-ca index.js

# index.js
const response = await fetch("https://internal.service.corp");
console.log(response.status);

process.report.getReport() is now implemented on Windows

The Node.js-compatible API process.report.getReport() is now fully implemented on Windows. Previously, this would throw a "Not implemented" error. This function generates a comprehensive diagnostic report about the current process, including system information, JavaScript heap statistics, stack traces, and loaded shared libraries.

This change improves compatibility with tools that rely on this API for diagnostics or environment detection.

// On Windows, this now returns a detailed report object.
const report = process.report.getReport();

console.log(report.header.osVersion); // e.g., "Windows 11 Pro"
console.log(report.header.cpus.length > 0); // true
console.log(report.javascript.heap.heapSpaces.length > 0); // true

Codesigning for Windows in bun build --compile

bun build --compile can create standalone executables from a JavaScript or TypeScript entrypoint. On Windows, these executables are often based on a pre-signed bun.exe binary. Previously, when Bun embedded the application code and assets, it would invalidate this original signature, preventing developers from applying their own code signing certificate.

This release introduces automatic "Authenticode" signature stripping. When you create an executable, Bun now removes the original signature, allowing you to sign the compiled binary with your own certificate using standard Windows tools like signtool.exe. This is essential for distributing trusted applications to Windows users.

# 1. Compile your application into a standalone executable
bun build ./index.ts --compile --outfile my-app.exe

# 2. Sign the resulting executable with your own certificate
signtool.exe sign /f MyCert.pfx /p MyPassword my-app.exe

Bun.build gets a new jsx configuration object

Configuration for JSX transforms in Bun.build is now centralized in a new jsx object. This includes options previously configured via tsconfig.json, like jsxFactory, jsxFragment, and jsxImportSource.

await Bun.build({
  entrypoints: ["./index.jsx"],
  outdir: "./dist",
  jsx: {
    runtime: "automatic", // "automatic" or "classic"
    importSource: "preact", // defaults to "react"
    factory: "h", // defaults to "React.createElement"
    fragment: "Fragment", // defaults to "React.Fragment"
    development: false, // use `jsx-dev` runtime, defaults to `false`
    sideEffects: false,
  },
});

sql.array helper in Bun.SQL

The sql.array helper in Bun.SQL makes it easy to work with PostgreSQL array types. You can insert arrays into array columns and specify the PostgreSQL data type for proper casting.

import { sql } from "bun";

// Insert an array of text values
await sql`
  INSERT INTO users (name, roles)
  VALUES (${"Alice"}, ${sql.array(["admin", "user"], "TEXT")})
`;

// Update with array values using sql object notation
await sql`
  UPDATE users
  SET ${sql({
    name: "Bob",
    roles: sql.array(["moderator", "user"], "TEXT"),
  })}
  WHERE id = ${userId}
`;

// Works with JSON/JSONB arrays
const jsonData = await sql`
  SELECT ${sql.array([{ a: 1 }, { b: 2 }], "JSONB")} as data
`;

// Supports various PostgreSQL types
await sql`SELECT ${sql.array([1, 2, 3], "INTEGER")} as numbers`;
await sql`SELECT ${sql.array([true, false], "BOOLEAN")} as flags`;
await sql`SELECT ${sql.array([new Date()], "TIMESTAMP")} as dates`;

The sql.array helper supports all major PostgreSQL array types including TEXT, INTEGER, BIGINT, BOOLEAN, JSON, JSONB, TIMESTAMP, UUID, INET, and many more.

Thanks to @cirospaciari for the contribution!

Top level await improvements

Bun's bundler now has improved support for top-level await.

When there are cylical dependencies using top-level await, Bun will now wrap modules in await Promise.all to ensure they are all loaded. We've also fixed a couple edgecases that could cause the async keyword to be missing from bundled modules in certain cases.

Thanks to @dylan-conway for implementing this!

Bundler & dev-server bugfixes:

  • Improved: syntax error messages for invalid escape sequences, like \", outside of string literals.
  • Fixed: A bug causing sourcemap line numbers to be off-by-one or more in Chrome DevTools
  • Fixed: A regression in the minifier where new Array() with a ternary expression would be bundled incorrectly has been fixed.
  • Fixed: bun build producing output that would hang indefinitely when bundling code with cyclic asynchronous module dependencies.
  • Fixed: a crash that occurred when a macro returned a complex or deeply-nested object or array.

bun install bugfixes:

  • Fixed: An integer overflow when parsing package versions, which could cause bun install to crash or select the wrong version of a package. This affected packages like @google/gemini-cli@nightly and @scratch/paper that use large numbers in their version strings.
  • Fixed a bug where bun install could fail with an ETXTBUSY (Text file busy) error when linking package binaries.
  • Fixed a crash on Windows when running bun outdated or bun install caused by a race condition.

bun test bugfixes:

  • Fixed: An uncaught promise rejection in an async test no longer causes the test runner to hang.
  • Fixed: test() and afterAll() are no longer allowed inside another test() callback. Previously, inner test callbacks were simply ignored. Bun will now throw an error instead of silently failing or producing unexpected behavior.
  • Fixed: When test.only is nested inside describe.only, only the innermost .only tests are executed.
  • Fixed: When using describe.only, beforeAll hooks in describe blocks not marked with .only will be correctly skipped.
  • Fixed: A failure in an async beforeEach hook now correctly prevents the corresponding test from running.
  • Fixed: Test hooks like beforeAll and beforeEach now support a timeout option and will fail if they exceed the specified duration.
  • Fixed: An afterAll hook will now run even if a corresponding beforeAll hook fails, which aligns with Jest's behavior and is useful for cleanup tasks.
  • Fixed: Throwing an error in a beforeAll hook loaded via --preload now correctly halts test execution across all files.
  • Fixed: When an error is thrown inside a describe block, any nested describe blocks are now correctly skipped.
  • Fixed: An issue where describe.todo() was incorrectly executed when nested inside describe.only().
  • Fixed: A passing test inside a describe.todo() block is now handled correctly.
  • Fixed: An error in an async beforeEach hook is now reported correctly, instead of being masked as an "unhandled error between tests".
  • Fixed: An exception when using custom matchers from libraries like jest-dom that rely on this.utils.RECEIVED_COLOR and this.utils.EXPECTED_COLOR. Additionally, vi.resetAllMocks, vi.useFakeTimers, and vi.useRealTimers are now stubbed until we finish implementing them.
  • Fixed: bun test now shows clearer error messages and help text for the --reporter and --coverage-reporter flags.
  • Fixed: A bug where bun test would not correctly load test files when using a . in the path.

Bug fixes and reliability improvements

  • Fixed: YAML.parse() now throws a SyntaxError for invalid input, matching the behavior of JSON.parse().
  • Fixed a bug where fetch() would only support single-frame zstd-compressed responses sent with Transfer-Encoding: chunked. Bun's decompressor now correctly handles multi-frame zstd streams, ensuring the full response body is received.
  • Fixed: A bug where fetch() with an AbortSignal would not abort if the signal was triggered while the underlying socket was connecting (only after it connected or failed to connect). This particularly affected AbortSignal.timeout() when making requests to unresponsive servers.
  • Fixed: A bug when console.logging an Error object could display a truncated stack trace.
  • Fixed a bug preventing Bun.redis from connecting to Redis/Valkey servers over TLS. This affected connections using rediss://or the tls: true option.
  • Fixed: infinite loop when logging an Error object with a circular reference, like error.stack = error or a circular error.cause chain.
  • Fixed a rare crash in Bun.serve that could occur during garbage collection.
  • Fixed a crash that occurred when a Bun.plugin's onResolve handler returned undefined or null.
  • Fixed an assertion failure on Windows caused by an incorrect file path.
  • Fixed: A bug in Bun.sql's postgres driver where NUMERIC values with many digits were parsed incorrectly has been resolved.
  • Fixed: The install.sh script now prioritizes ~/.bash_profile over ~/.bashrc when updating the PATH, aligning with standard Bash practices.
  • Internal: LeakSanitizer to our CI pipeline to automatically detect native memory leaks.
  • Fixed: A crash that could occur when a UDP socket is active while the Bun process is exiting.
  • Fixed: A very rare race condition that could cause fetch() to crash when handling many simultaneous redirects while an AbortSignal was active.
  • Fixed: A stability issue in Bun.serve impacting large request bodies
  • Fixed: BUN_CONFIG_VERBOSE_FETCH=curl now prints the request body for fetch requests with Content-Type: application/x-www-form-urlencoded.
  • Fixed a crash when a large number of command-line arguments were passed to the current process and process.argv was accessed.
  • Fixed: The current working directory in stack traces is now dimmed, improving readability and helping to identify local files.
  • Fixed: Bun.SQL's MySQL driver did not work on Windows.
  • Upgraded libuv to v1.51.0, improving I/O reliability
  • Fixed: Bun's browser error modal is now 250 KB smaller, making it faster to load.
  • Fixed an issue where npm install bun would fail on Alpine Linux for arm64.

Thanks to 16 contributors!