Bun

Bun v1.2.20


Jarred Sumner · August 10, 2025

This release fixes 141 issues (addressing 429 👍) and includes many reliability improvements throughout the runtime, the bundler, and the dev server.

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

Reduced idle CPU usage

Over-scheduling the garbage collector when idling had a serious impact on idle CPU usage.

This change had a neutral impact on memory usage.

Automatic yarn.lock migration

bun install will now automatically migrate your yarn.lock (v1) file to a bun.lock file.

Thanks to @RiskyMH for the contribution

40x faster AbortSignal.timeout

We rewrote AbortSignal.timeout to use the same underlying implementation as setTimeout. This makes it 40x faster.

40x faster `AbortSignal.timeout`

Improved bun:test diffing

The diffing output in bun:test has been redesigned for improved readability.

Whitespace differences are highlighted.

bun:test diffing

Several edgecases involving non-ascii characters and numerous other bugs have been fixed.

Thanks to @pfgithub for the contribution!

New bun:test matchers for return values

Three new bun:test matchers have been added for asserting the return values of mock functions: toHaveReturnedWith, toHaveLastReturnedWith, and toHaveNthReturnedWith. These matchers use deep equality, similar to toEqual(), and support asymmetric matchers.

  • toHaveReturnedWith(): Checks if the mock function has returned a specific value in any of its calls.
  • toHaveLastReturnedWith(): Checks the return value of the most recent call.
  • toHaveNthReturnedWith(): Checks the return value of a specific call (1-indexed).
import { test, expect, mock } from "bun:test";

test("toHaveReturnedWith", () => {
  const returnsAnObject = mock(() => ({ a: 1 }));
  returnsAnObject();
  expect(returnsAnObject).toHaveReturnedWith({ a: 1 });
});

test("toHaveLastReturnedWith", () => {
  const returnsAString = mock((i) => `call ${i}`);
  returnsAString(1);
  returnsAString(2);
  expect(returnsAString).toHaveLastReturnedWith("call 2");
});

test("toHaveNthReturnedWith", () => {
  const returnsANumber = mock((i) => i * 10);
  returnsANumber(1);
  returnsANumber(2);
  returnsANumber(3);
  expect(returnsANumber).toHaveNthReturnedWith(2, 20);
});

Thanks to @dylan-conway for the contribution!

Test your types with expectTypeOf

bun:test now includes expectTypeOf for asserting TypeScript types, with an API compatible with Vitest.

These assertions are no-ops at runtime; they are checked by the TypeScript compiler. To verify your type tests, run bunx tsc --noEmit.

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

test("type-level tests", () => {
  // Basic type assertions
  expectTypeOf("hello").toBeString();
  expectTypeOf(123).toBeNumber();

  // Check object shapes
  expectTypeOf({ a: 1, b: "2" }).toMatchObjectType<{ a: number }>();

  // Assert function parameter and return types
  function add(a: number, b: number): number {
    return a + b;
  }

  expectTypeOf(add).parameters.toEqualTypeOf<[number, number]>();
  expectTypeOf(add).returns.toBeNumber();
});

Thanks to @pfgithub for the contribution

mock.clearAllMocks() for bun:test

The bun:test module now implements mock.clearAllMocks(), a function to reset the state of all mocked functions. This function clears the .mock.calls and .mock.results properties of all mocks, but importantly, it does not restore their original implementations.

This is useful for resetting mock states between tests, for instance in a global setup file, without needing to manually track and clear each individual mock.

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

const random = mock(() => Math.random());

test("clearing all mocks", () => {
  random();
  expect(random).toHaveBeenCalledTimes(1);

  // Reset the state of all mocks
  mock.clearAllMocks();

  expect(random).toHaveBeenCalledTimes(0);

  // The mock implementation is preserved
  expect(typeof random()).toBe("number");
  expect(random).toHaveBeenCalledTimes(1);
});

Thanks to @pfgithub for the contribution

bun outdated and bun update now supports --recursive

bun outdated and bun update --interactive now have improved support for workspaces, making it easier to manage dependencies in monorepos.

You can now run these commands across all workspaces using the -r or --recursive flag.

# See outdated packages in all workspaces
bun outdated --recursive

# Interactively update packages in all workspaces
bun update -i -r

When running bun update -i in a monorepo, a new "Workspace" column will be displayed, showing which workspace a dependency belongs to:

bun update -i --recursive

dependencies             Current  Target  Latest   Workspace
  ❯ □ @types/node        24.1.0   24.2.1  24.2.1   bun-types

Additionally, the --filter flag is now supported in bun update -i, allowing you to scope updates to specific workspaces.

# Interactively update dependencies only in the "my-app" workspace
bun update -i --filter="my-app"

This also fixes issues where bun update would not correctly handle catalog dependencies. Additionally bun outdated and bun update -i shows which packages use the catelog in the workspace column.

Thanks to @RiskyMH for the contribution

Automatic ETag and If-None-Match in static routes of Bun.serve

Bun.serve now automatically generates ETag headers for static routes defined in the static option. When a client sends an If-None-Match header, Bun compares the ETag and sends a 304 Not Modified response if the content is unchanged. This improves caching efficiency and saves bandwidth, with no code changes required to enable it.

const server = Bun.serve({
  port: 0,
  routes: {
    "/latest.json": Response.json({ ...myBigObject }),
  },
});
const url = new URL("/latest.json", server.url);
const etag = await fetch(url).then((res) => res.headers.get("etag"));
const { status } = await fetch(url, {
  headers: {
    "If-None-Match": etag,
  },
});

console.log({ status, etag });

Windows long path support

Bun now consistently supports file paths longer than 260 characters on Windows. This is enabled via an application manifest, removing a common source of file-related errors in projects with deep directory structures or long file names.

import { mkdirSync, existsSync, rmSync } from "fs";
import { join } from "path";

// This path is longer than 260 characters
const longPath = join("C:\\", "a".repeat(270));

// This could've previously failed on Windows
mkdirSync(longPath, { recursive: true });

console.log(`Exists: ${existsSync(longPath)}`); // Exists: true

rmSync(longPath, { recursive: true, force: true });

Previously, supporting long file paths on Windows involved extremely complicated file path namespacing we internally added to most of our code. Now it's much simpler, since the Win32 API supports long paths natively.

WebAssembly.compileStreaming and WebAssembly.instantiateStreaming

Bun now supports WebAssembly.compileStreaming() and WebAssembly.instantiateStreaming(). These APIs allow you to compile and instantiate a WebAssembly module directly from a streaming source, like the Response from a fetch() call.

This approach is more efficient than the non-streaming alternatives (WebAssembly.compile and WebAssembly.instantiate), as it avoids buffering the entire Wasm module into memory. Compilation can start as soon as the first bytes are received, reducing latency and memory usage.

// Bun will stream the response body directly to the Wasm compiler.
const { instance, module } = await WebAssembly.instantiateStreaming(
  fetch("http://localhost:3000/add.wasm"),
);

// Use the instantiated module
console.log(instance.exports.add(2, 3)); // => 5

Thanks to @CountBleck for the contribution

Node.js compatibility improvements

  • Fixed: A type error when assigning to require.main due to an outdated type definition. The main property is no longer readonly.
  • Fixed: Invalid source map errors were incorrectly logged when running next dev --turbopack.
  • Fixed: cancelling an HTTP request, such as by rapidly refreshing a page in next dev, could cause an ERR_STREAM_ALREADY_FINISHED error
  • Fixed: a thread-safety issue when using Zstandard streams with the zlib module.
  • Fixed: a potential crash in node:crypto's X509Certificate when invalid input was provided.
  • Fixed: a hypothetical bug where an uncaught exception inside process.nextTick itself or a microtask would not be reported
  • Fixed: memory leak in fs.mkdir and fs.mkdirSync with { recursive: true } has been resolved.
  • Fixed: missing [Symbol.asyncIterator] in process.stdout and process.stderr when it's a TTY or pipe. This bug impacted some users of Claude Code on Linux.

Bun shell fixes

  • Fixed: a crash in bun shell when using pipelines with built-in commands that exit immediately
  • Fixed: $.braces() now supports patterns containing Unicode characters and more deeply nested expansions.
  • Fixed: Shell lexer would incorrectly display the = token as + in error messages.
  • Fixed: a crash when parsing invalid syntax in certain cases.

Bundler bugfixes

  • Fixed: a stack overflow in the JavaScript parser when parsing deeply nested expressions. This was resolved by refactoring the parser to use less stack space, preventing crashes with long chains of member accesses or function calls.
  • Fixed: a bug where bun build would generate invalid code for cyclic imports containing a top-level await dependency, causing a syntax error.

bun install bugfixes

  • Fixed: a bug with --linker=isolated when running lifecycle scripts with paths that contain non-ASCII characters.
  • Fixed: a crash when a permissions error during installation of a dependency in bun install that could occur in non-interactive environments like GitHub Actions.

Frontend dev-server bugfixes

  • Fixed: a crash in --hot mode when using vim-like swapfiles that could occur when a file's imports are changed or removed.
  • Fixed: a crash in the dev server with --hot enabled when deleting a file imported by multiple other files.
  • Fixed: a crash in the file watcher on Windows that could occur when many files changed at once, such as when switching git branches. This was caused by an index-out-of-bounds error when the number of file system events exceeded an internal buffer.
  • Fixed: a crash that could occur when a client aborts an HTTP request.
  • Fixed: a potential performance bottleneck in the dev server by improving internal buffer management for path resolution.
  • Fixed a bug where import.meta.url was incorrect in the browser when using Hot Module Replacement (HMR). It now correctly uses window.location.origin instead of bun://.

bun:test bugfixes

  • Fixed: expect(...).toHaveBeenCalledWith() and related mock function matchers now show a colorful diff when assertions fail, making it much easier to debug tests with complex object arguments.
  • Fixed: beforeAll hooks in bun test would run for describe blocks that did not contain any tests matching the current test filter.
  • Fixed: bug with expect(() => { throw undefined }).toThrow(ErrorClass)
  • Fixed: jest.fn().mockRejectedValue() would cause an unhandled rejection if the mocked function was never called.
  • Fixed: beforeAll hooks in bun test would run for describe blocks even when all tests within that block were filtered out.
  • Fixed: bun test would not correctly filter tests when a directory name was passed as an argument.
  • Fixed: bun test would display the warning for a passing test.failing() on the wrong line.
  • Fixed: toIncludeRepeated in bun:test checked for at least the specified number of occurrences instead of the exact number, aligning Bun's behavior with Jest.
  • Fixed: bun test would fail when running multiple test files using the node:test module.

Windows bugfixes

  • Fixed: an assertion failure on Windows when resolving an invalid file path.
  • Fixed: an integer cast panic when reading large files on Windows

Runtime bugfixes

  • Fixed: terminal syntax highlighter that would incorrectly add an extra closing brace } to error messages involving template literals.
  • Improved: Transfer-Encoding header validation with duplicate values. Thanks to Radman Siddiki for the report.

Bun.SQL

  • Fixed: a crash in Bun.SQL that could occur when a database connection fails.
  • Fixed: an "index out of bounds" that could occur during large batch inserts.
  • Fixed: a bug in Bun.sql client that could cause the process to hang indefinitely, particularly when queries were pending during shutdown.

File system and Bun.file

  • Fixed: calling .write(), .writer(), .unlink(), or .delete() on a read-only Blob (one not created via Bun.file()) would not throw an error. It now correctly throws an error stating that Blobs backed by bytes are read-only, matching the behavior of Bun.write().

Bun.which and executable resolution

  • Fixed: Bun.which on Windows failed to find executables in directory paths containing non-ASCII characters. This impacted lifecycle scripts in bun install as well.

Bun.resolve and module resolution

  • Fixed: Bun.resolve() and Bun.resolveSync() now consistently throw Error objects on failure. Previously, they could throw raw values, causing crashes in try...catch blocks.

Bun.s3

  • Fixed a crash that could occur in Bun.s3.unlink() when S3 credentials were not configured.

Response constructor and Web APIs

  • Fixed: string coercion of statusText to the Response constructor
  • Fixed: Response.redirect() will now correctly throw a RangeError when an invalid status code is provided.

Environment variables and configuration

  • Fixed a bug where environment variables loaded from .env files would be truncated if they were longer than 4096 characters. This could also cause a crash.

Installation and platform-specific fixes

  • Fixed a bug in the Windows install script (install.ps1) that incorrectly joined the local script's PATH environment variable by joining paths with spaces instead of semicolons.

Memory management and performance

  • Bun's threadpool now releases memory more aggressively after 10 seconds of inactivity, reducing memory usage during idle periods.
  • Optimized an internal synchronization primitive, WaitGroup, by replacing mutex locks with atomic operations. This can improve performance in scenarios with a high number of concurrent tasks.

TypeScript type improvements

  • Fixed: @types/bun is now compatible with TypeScript 5.9, resolving a type conflict with ArrayBuffer that previously required skipLibCheck: true in tsconfig.json.
  • In bun:sqlite, TypeScript types for db.transaction() have been improved to correctly infer the return type from the transaction callback. This allows you to return data from within a transaction in a type-safe way. The arguments passed to the transaction are also correctly typed.

Thanks to 19 contributors!