Remove bmalloc.lib from Windows link targets in both DEBUG and RELEASE
builds. libpas was originally disabled on Windows in PR #20931 due to
stability issues (pas_assertion_failed crashes after extended runtime),
but was inadvertently re-enabled in PR #26381 ("Update WebKit").
No changes were made to fix the underlying libpas stability issues on
Windows between the disable and re-enable, so the same class of crashes
(documented in #21192, #24576, #20505) resurfaced.
Fixes#26982
Co-Authored-By: Claude <noreply@anthropic.com>
## Summary
- Fix out-of-bounds read in the INI parser's `prepareStr` function when
a multi-byte UTF-8 lead byte appears at the end of a value with
insufficient continuation bytes
- Fix undefined behavior when bare continuation bytes (0x80-0xBF) cause
`utf8ByteSequenceLength` to return 0, hitting an `unreachable` branch
(UB in ReleaseFast builds)
- Add bounds checking before accessing `val[i+1]`, `val[i+2]`,
`val[i+3]` in both escaped and non-escaped code paths
The vulnerability could be triggered by a crafted `.npmrc` file
containing truncated UTF-8 sequences. In release builds, this could
cause OOB heap reads (potential info leak) or undefined behavior.
## Test plan
- [x] Added 9 tests covering truncated 2/3/4-byte sequences, bare
continuation bytes, and escaped contexts
- [x] All 52 INI parser tests pass (`bun bd test
test/js/bun/ini/ini.test.ts`)
- [x] No regressions in npmrc tests (failures are pre-existing Verdaccio
connectivity issues)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Reject null bytes in `username`, `password`, `database`, and `path`
connection parameters for both PostgreSQL and MySQL to prevent wire
protocol parameter injection
- Both the Postgres and MySQL wire protocols use null-terminated strings
in their startup/handshake messages, so embedded null bytes in these
fields act as field terminators, allowing injection of arbitrary
protocol parameters (e.g. `search_path` for schema hijacking)
- The fix validates these fields immediately after UTF-8 conversion and
throws `InvalidArguments` error with a clear message if null bytes are
found
## Test plan
- [x] New test
`test/regression/issue/postgres-null-byte-injection.test.ts` verifies:
- Null bytes in username are rejected with an error before any data is
sent
- Null bytes in database are rejected with an error before any data is
sent
- Null bytes in password are rejected with an error before any data is
sent
- Normal connections without null bytes still work correctly
- [x] Test verified to fail with `USE_SYSTEM_BUN=1` (unfixed bun) and
pass with `bun bd test` (fixed build)
- [x] Existing SQL tests pass (`adapter-env-var-precedence.test.ts`,
`postgres-stringbuilder-assertion-aggressive.test.ts`)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Fix double-free in `Bun.plugin.clearAll()` by setting `virtualModules
= nullptr` after `delete`
- In `jsFunctionBunPluginClear` (`BunPlugin.cpp:956`), `delete
global->onLoadPlugins.virtualModules` freed the pointer without
nullifying it. When the `OnLoad` destructor later runs (during Worker
termination or VM destruction), it checks `if (virtualModules)` — the
dangling non-null pointer passes the check and is deleted again,
corrupting the heap allocator.
## Test plan
- [ ] New test
`test/regression/issue/plugin-clearall-double-free.test.ts` spawns a
subprocess that registers a virtual module, calls
`Bun.plugin.clearAll()`, and exits with `BUN_DESTRUCT_VM_ON_EXIT=1` to
trigger the destructor path
- [ ] Verified the test fails on the system bun (pre-fix) with `pas
panic: deallocation did fail ... Alloc bit not set`
- [ ] Verified the test passes with the debug build (post-fix)
- [ ] Existing plugin tests (`test/js/bun/plugin/plugins.test.ts`) all
pass (29/29)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
- Fix WebSocket client pong frame handler to properly handle payloads
split across TCP segments, preventing frame desync that could cause
protocol confusion
- Add missing RFC 6455 Section 5.5 validation: control frame payloads
must not exceed 125 bytes (pong handler lacked this check, unlike ping
and close handlers)
## Details
The pong handler (lines 652-663) had two issues:
1. **Frame desync on fragmented delivery**: When a pong payload was
split across TCP segments (`data.len < receive_body_remain`), the
handler consumed only the available bytes but unconditionally reset
`receive_state = .need_header` and `receive_body_remain = 0`. The
remaining payload bytes in the next TCP delivery were then
misinterpreted as WebSocket frame headers.
2. **Missing payload length validation**: Unlike the ping handler (line
615) and close handler (line 680), the pong handler did not validate the
7-bit payload length against the RFC 6455 limit of 125 bytes for control
frames.
The fix models the pong handler after the existing ping handler pattern:
track partial delivery state with a `pong_received` boolean, buffer
incoming data into `ping_frame_bytes`, and only reset to `need_header`
after the complete payload has been consumed.
## Test plan
- [x] New test `websocket-pong-fragmented.test.ts` verifies:
- Fragmented pong delivery (50-byte payload split into 2+48 bytes) does
not cause frame desync, and a subsequent text frame is received
correctly
- Pong frames with >125 byte payloads are rejected as invalid control
frames
- [x] Test fails with `USE_SYSTEM_BUN=1` (reproduces the bug) and passes
with `bun bd test`
- [x] Existing WebSocket tests pass: `websocket-client.test.ts`,
`websocket-close-fragmented.test.ts`,
`websocket-client-short-read.test.ts`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Fixes HTTP header injection vulnerability in S3 client where
user-controlled options (`contentDisposition`, `contentEncoding`,
`type`) were passed to HTTP headers without CRLF validation
- Adds input validation at the JS-to-Zig boundary in
`src/s3/credentials.zig` that throws a `TypeError` if `\r` or `\n`
characters are detected
- An attacker could previously inject arbitrary headers (e.g.
`X-Amz-Security-Token`) by embedding `\r\n` in these string fields
## Test plan
- [x] Added `test/regression/issue/s3-header-injection.test.ts` with 6
tests:
- CRLF in `contentDisposition` throws
- CRLF in `contentEncoding` throws
- CRLF in `type` (content-type) throws
- Lone CR in `contentDisposition` throws
- Lone LF in `contentDisposition` throws
- Valid `contentDisposition` without CRLF still works correctly
- [x] Tests fail with `USE_SYSTEM_BUN=1` (confirming vulnerability
exists in current release)
- [x] Tests pass with `bun bd test` (confirming fix works)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Fix path traversal vulnerability in tarball directory extraction on
POSIX systems where `mkdiratZ` used the un-normalized `pathname` (raw
from tarball) instead of the normalized `path` variable, allowing `../`
components to escape the extraction root via kernel path resolution
- The Windows directory creation, symlink creation, and file creation
code paths already correctly used the normalized path — only the two
POSIX `mkdiratZ` calls were affected (lines 463 and 469)
- `bun install` is not affected because npm mode skips directory
entries; affected callers include `bun create`, GitHub tarball
extraction, and `compile_target`
## Test plan
- [x] Added regression test that crafts a tarball with
`safe_dir/../../escaped_dir/` directory entry and verifies it cannot
create directories outside the extraction root
- [x] Verified test **fails** with system bun (vulnerable) and
**passes** with debug build (fixed)
- [x] Full `archive.test.ts` suite passes (99/99 tests)
- [x] `symlink-path-traversal.test.ts` continues to pass (3/3 tests)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Replace `bun.strings.eqlLong` with BoringSSL's `CRYPTO_memcmp` for
SCRAM-SHA-256 server signature verification in the PostgreSQL client
- The previous comparison (`eqlLong`) returned early on the first
mismatching byte, potentially leaking information about the expected
server signature via timing side-channel
- `CRYPTO_memcmp` is already used elsewhere in the codebase for
constant-time comparisons (CSRF tokens, `crypto.timingSafeEqual`,
KeyObject comparison)
## Test plan
- [x] `bun bd` compiles successfully
- [ ] Existing SCRAM-SHA-256 integration tests in
`test/js/sql/sql.test.ts` pass (require Docker/PostgreSQL)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Fixes#26918 — segfault at address `0x28189480080` caused by
use-after-free in the shell interpreter
- When `setupIOBeforeRun()` fails (e.g., stdout handle unavailable on
Windows), the `runFromJS` error path called `deinitFromExec()` which
directly freed the GC-managed interpreter object with
`allocator.destroy(this)`. When the GC later swept and called
`deinitFromFinalizer()` on the already-freed memory, it caused a
segfault.
- Replaced `deinitFromExec()` with `derefRootShellAndIOIfNeeded(true)`
which properly cleans up runtime resources (IO handles, shell
environment) while leaving final object destruction to the GC finalizer
— matching the pattern already used in `finish()`.
## Test plan
- [x] Added regression test in `test/regression/issue/26918.test.ts`
that verifies the shell interpreter handles closed stdout gracefully
without crashing
- [x] Test passes with `bun bd test test/regression/issue/26918.test.ts`
- [ ] The actual crash is primarily reproducible on Windows where stdout
handles can be truly unavailable — CI Windows tests should validate the
fix
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
- When `--bail` caused an early exit after a test failure, the JUnit
reporter output file (`--reporter-outfile`) was never written because
`Global.exit()` was called before the normal completion path
- Extracted the JUnit write logic into a `writeJUnitReportIfNeeded()`
method on `CommandLineReporter` and call it in both bail exit paths
(test failure and unhandled rejection) as well as the normal completion
path
Closes#26851
## Test plan
- [x] Added regression test `test/regression/issue/26851.test.ts` with
two cases:
- Single failing test file with `--bail` produces JUnit XML output
- Multiple test files where bail triggers on second file still writes
the report
- [x] Verified test fails with system bun (`USE_SYSTEM_BUN=1`)
- [x] Verified test passes with `bun bd test`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Add `expectMessageEventually(value)` to the bake test harness `Client`
class — waits for a specific message to appear, draining any
intermediate messages that arrived before it
- Rewrite "hmr handles rapid consecutive edits" test to use raw
`Bun.write` + sleep for intermediate edits and `expectMessageEventually`
for the final assertion, avoiding flaky failures when HMR batches
updates non-deterministically across platforms
Fixes flaky failure on Windows where an extra "render 10" message
arrived after `expectMessage` consumed its expected messages but before
client disposal.
## Test plan
- [x] `bun bd test test/bake/dev-and-prod.test.ts` — all 12 tests pass
- [x] Ran the specific test multiple times to confirm no flakiness
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Alistair Smith <alistair@anthropic.com>
## Problem
The bundler's number renamer was mangling `.name` properties on crypto
class prototype methods and constructors:
- `hash.update.name` → `"update2"` instead of `"update"`
- `verify.verify.name` → `"verify2"` instead of `"verify"`
- `cipher.update.name` → `"update3"` instead of `"update"`
- `crypto.Hash.name` → `"Hash2"` instead of `"Hash"`
### Root causes
1. **Named function expressions on prototypes** collided with other
bindings after scope flattening (e.g. `Verify.prototype.verify =
function verify(...)` collided with the imported `verify`)
2. **Block-scoped constructor declarations** (`Hash`, `Hmac`) got
renamed when the bundler hoisted them out of block scope
3. **Shared function declarations** in the Cipher/Decipher block all got
numeric suffixes (`update3`, `final2`, `setAutoPadding2`, etc.)
## Fix
- Use `Object.assign` with object literals for prototype methods —
object literal property keys correctly infer `.name` and aren't subject
to the renamer
- Remove unnecessary block scopes around `Hash` and `Hmac` constructors
so they stay at module level and don't get renamed
- Inline `Cipheriv` methods and copy references to `Decipheriv`
## Tests
Added comprehensive `.name` tests for all crypto classes: Hash, Hmac,
Sign, Verify, Cipheriv, Decipheriv, DiffieHellman, ECDH, plus factory
functions and constructor names.
## What does this PR do?
Fixes the write loop in `StandaloneModuleGraph.inject()` for POSIX
targets (the `else` branch handling ELF/Linux standalone binaries) to
pass `remain` instead of `bytes` to `Syscall.write()`.
## Problem
The write loop that appends the bundled module graph to the end of the
executable uses a standard partial-write retry pattern, but passes the
full `bytes` buffer on every iteration instead of the remaining portion:
```zig
var remain = bytes;
while (remain.len > 0) {
switch (Syscall.write(cloned_executable_fd, bytes)) { // bug: should be 'remain'
.result => |written| remain = remain[written..],
...
}
}
```
If a partial write occurs, the next iteration re-writes from the start
of the buffer instead of continuing where it left off, corrupting the
output binary. The analogous read loop elsewhere in the same file
already correctly uses `remain`.
## Fix
One-character change: `bytes` → `remain` in the `Syscall.write()` call.
## How did you verify your code works?
- `bun bd` compiles successfully
- `bun bd test test/bundler/bun-build-compile.test.ts` — 4/4 pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alistair Smith <alistair@anthropic.com>
## What does this PR do?
Fixes `bun build --compile` producing an all-zeros binary when the
output directory is on a different filesystem than the temp directory.
This is common in Docker containers, Gitea runners, and other
environments using overlayfs.
## Problem
When `inject()` finishes writing the modified executable to the temp
file, the file descriptor's offset is at EOF. If the subsequent
`renameat()` to the output path fails with `EXDEV` (cross-device — the
temp file and output dir are on different filesystems), the code falls
back to `copyFileZSlowWithHandle()`, which:
1. Calls `fallocate()` to pre-allocate the output file to the correct
size (filled with zeros)
2. Calls `bun.copyFile(in_handle, out_handle)` — but `in_handle`'s
offset is at EOF
3. `copy_file_range` / `sendfile` / `read` all use the current file
offset (EOF), read 0 bytes, and return immediately
4. Result: output file is the correct size but entirely zeros
This explains user reports of `bun build --compile
--target=bun-darwin-arm64` producing invalid binaries that `file`
identifies as "data" rather than a Mach-O executable.
## Fix
Seek the input fd to offset 0 in `copyFileZSlowWithHandle` before
calling `bun.copyFile`.
## How did you verify your code works?
- `bun bd` compiles successfully
- `bun bd test test/bundler/bun-build-compile.test.ts` — 6/6 pass
- Added tests that verify compiled binaries have valid executable
headers and produce correct output
- Manually verified cross-compilation: `bun build --compile
--target=bun-darwin-arm64` produces a valid Mach-O binary
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Update inline error snapshots in valkey reliability tests to match
Redis 8's changed error message format
- Redis 8 (`redis:8-alpine` in our test Docker container) no longer
appends `, with args beginning with: ` when an unknown command has no
arguments
## Root cause
Redis commit
[`25f780b6`](25f780b662)
([PR #14690](https://github.com/redis/redis/pull/14690)) changed
`commandCheckExistence()` in `src/server.c` to only append `, with args
beginning with: ` when there are actual arguments (`c->argc >= 2`).
Previously it was always appended, producing a dangling `, with args
beginning with: ` even with zero arguments.
## Changes
- `test/js/valkey/reliability/protocol-handling.test.ts`: Updated
`SYNTAX-ERROR` snapshot (no args case)
- `test/js/valkey/reliability/error-handling.test.ts`: Updated
`undefined` and `123` snapshots (no args cases)
## Test plan
- [ ] Verify `protocol-handling.test.ts` passes in CI (was failing on
every attempt as shown in #26869 / build #36831)
- [ ] Verify `error-handling.test.ts` passes in CI
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
#### (Copies commits from #26447)
## Summary
- Add a global `--retry <N>` flag to `bun test` that sets a default
retry count for all tests (overridable by per-test `{ retry: N }`). Also
configurable via `[test] retry = N` in bunfig.toml.
- When a test passes after one or more retries, the JUnit XML reporter
emits a separate `<testcase>` entry for each failed attempt (with
`<failure>`), followed by the final passing `<testcase>`. This gives
flaky test detection tools per-attempt timing and result data using
standard JUnit XML that all CI systems can parse.
## Test plan
- `bun bd test test/js/junit-reporter/junit.test.js` — verifies separate
`<testcase>` entries appear in JUnit XML for tests that pass after retry
- `bun bd test test/cli/test/retry-flag.test.ts` — verifies the
`--retry` CLI flag applies a default retry count to all tests
## Changelog
<!-- CHANGELOG:START -->
- Added `--retry <N>` flag to `bun test` to set a default retry count
for all tests
- Added `[test] retry` option to bunfig.toml
- JUnit XML reporter now emits separate `<testcase>` entries for each
retry attempt, providing CI visibility into flaky tests
<!-- CHANGELOG:END -->
---------
Co-authored-by: Chris Lloyd <chrislloyd@anthropic.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
Fixes "Unknown HMR script" error during rapid consecutive edits in HMR
## Test plan
- [x] Basic consecutive HMR edits work correctly
---------
Co-authored-by: Alistair Smith <alistair@anthropic.com>
## Summary
- Convert `file:` URL strings to filesystem paths via
`Bun.fileURLToPath()` in the JS layer for both `fs.watch` and
`fs.watchFile`/`fs.unwatchFile`
- Handles percent-decoding (e.g. `%20` → space) and proper URL parsing,
which the previous naive `slice[6..]` stripping in Zig could not do
- Zig-level `file://` stripping is left unchanged; the JS layer now
resolves file URLs before they reach native code
## Test plan
- [x] New test: `fs.watch` with `file:` URL string containing
`%20`-encoded spaces
- [x] New test: `fs.watchFile` with `file:` URL string containing
`%20`-encoded spaces
- [x] Both tests fail with `USE_SYSTEM_BUN=1` and pass with `bun bd
test`
- [x] Existing `fs.watch` "should work with url" test (URL object) still
passes
- [x] Full `fs.watchFile` suite passes (6 pass, 1 skip, 0 fail)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Implement complete lowering of TC39 stage-3 standard ES decorators
(the non-legacy variant used when tsconfig has no
`experimentalDecorators`).
- Passes all 147 esbuild decorator tests and 22 additional Bun-specific
tests (191 total, 0 failures).
- Supports method, getter, setter, field, auto-accessor, private member,
and class decorators in both statement and expression positions, with
proper evaluation order, class binding semantics, and decorator
metadata.
Fixes#4122Fixes#20206Fixes#14529Fixes#6051
## What's implemented
| Feature | Details |
|---|---|
| Method/getter/setter decorators | Static and instance, public and
private |
| Field decorators | Initializer replacement + extra initializers via
`__runInitializers` |
| Auto-accessor (`accessor` keyword) | Lowered to WeakMap storage +
getter/setter pair |
| Private member decorators | WeakMap/WeakSet lowering with
`__privateGet`/`__privateSet` |
| Class decorators | Statement and expression positions |
| Class expression decorators | Comma-expression lowering (no IIFE) |
| Decorator metadata | `Symbol.metadata` support via
`__decoratorMetadata` |
| Evaluation order | All decorator expressions + computed keys evaluated
in source order per TC39 spec |
| Class binding semantics | Separate inner/outer class name bindings
(element vs class decorator closures) |
| Static block extraction | `this` replaced with class name ref when
moved to suffix |
| Computed property keys | Pre-evaluated into temp variables for correct
ordering |
## Runtime helpers
Added to `src/runtime.js` and registered in `src/runtime.zig`:
- `__decoratorStart(base)` — creates decorator context array
- `__decorateElement(array, flags, name, decorators, target, extra)` —
applies decorators to a class element
- `__decoratorMetadata(array, target)` — sets `Symbol.metadata` on the
class
- `__runInitializers(array, flags, self, value)` — runs
initializer/extra-initializer arrays
## Test plan
- [x] `bun bd test
test/bundler/transpiler/es-decorators-esbuild.test.ts` — **147/147
pass** (esbuild's full decorator test suite)
- [x] `bun bd test test/bundler/transpiler/es-decorators.test.ts` —
**22/22 pass**
- [x] `bun bd test test/bundler/transpiler/decorators.test.ts` — **22/22
pass** (legacy decorators still work)
- [x] E2E runtime verification of method, field, accessor, class,
private, and expression decorators
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
## Summary
Adds a **DenseArray fast path** for `structuredClone` / `postMessage`
that completely skips byte-buffer serialization when an
`ArrayWithContiguous` array contains **flat objects** (whose property
values are only primitives or strings).
This builds on #26814 which added fast paths for Int32/Double/Contiguous
arrays of primitives and strings. The main remaining slow case was
**arrays of objects** — the most common real-world pattern (e.g.
`[{name: "Alice", age: 30}, {name: "Bob", age: 25}]`). Previously, these
fell back to the full serialization path because the Contiguous fast
path rejected non-string cell elements.
## How it works
### Serialization
The existing Contiguous array handler is extended to recognize object
elements that pass `isObjectFastPathCandidate` (FinalObject, no
getters/setters, no indexed properties, all enumerable). For qualifying
objects, properties are collected into a `SimpleCloneableObject` struct
(reusing the existing `SimpleInMemoryPropertyTableEntry` type). The
result is stored as a `FixedVector<DenseArrayElement>` where
`DenseArrayElement = std::variant<JSValue, String,
SimpleCloneableObject>`.
If no object elements are found, the existing `SimpleArray` path is used
(no regression).
### Deserialization
A **Structure cache** avoids repeated Structure transitions when the
array contains many same-shape objects (the common case). The first
object is built via `constructEmptyObject` + `putDirect`, and its final
Structure + Identifiers are cached. Subsequent objects with matching
property names are created directly with `JSFinalObject::create(vm,
cachedStructure)`, skipping all transitions.
Safety guards:
- Cache is only used when property count AND all property names match
- Cache is disabled when `outOfLineCapacity() > 0` (properties exceed
`maxInlineCapacity`), since `JSFinalObject::create` cannot allocate a
butterfly
### Fallback conditions
| Condition | Behavior |
|-----------|----------|
| Elements are only primitives/strings | SimpleArray (existing) |
| Elements include `isObjectFastPathCandidate` objects | **DenseArray
(NEW)** |
| Object property value is an object/array | Fallback to normal path |
| Elements include Date, RegExp, Map, Set, ArrayBuffer, etc. | Fallback
to normal path |
| Array has holes | Fallback to normal path |
## Benchmarks
Apple M4 Max, release build vs system Bun v1.3.8 and Node.js v24.12:
| Benchmark | Node.js v24.12 | Bun v1.3.8 | **This PR** | vs Bun | vs
Node |
|-----------|---------------|------------|-------------|--------|---------|
| `[10 objects]` | 2.83 µs | 2.72 µs | **1.56 µs** | **1.7x** | **1.8x**
|
| `[100 objects]` | 24.51 µs | 25.98 µs | **14.11 µs** | **1.8x** |
**1.7x** |
## Test coverage
28 new edge-case tests covering:
- **Property value variants**: empty objects, special numbers (NaN,
Infinity, -0), null/undefined values, empty string keys, boolean-only
values, numeric string keys
- **Structure cache correctness**: alternating shapes, objects
interleaved with primitives, >maxInlineCapacity properties (100+), 1000
same-shape objects (stress test), repeated clone independence
- **Fallback correctness**: array property values, nested objects,
Date/RegExp/Map/Set/ArrayBuffer elements, getters, non-enumerable
properties, `Object.create(null)`, class instances
- **Frozen/sealed**: clones are mutable regardless of source
- **postMessage via MessageChannel**: mixed arrays with objects, empty
object arrays
## Changed files
- `src/bun.js/bindings/webcore/SerializedScriptValue.h` —
`SimpleCloneableObject`, `DenseArrayElement`, `FastPath::DenseArray`,
factory/constructor/member
- `src/bun.js/bindings/webcore/SerializedScriptValue.cpp` — serialize,
deserialize, `computeMemoryCost`
- `test/js/web/structured-clone-fastpath.test.ts` — 28 new tests
- `bench/snippets/structuredClone.mjs` — object array benchmarks
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
Follow-up to #26819 ([review
comment](https://github.com/oven-sh/bun/pull/26819#discussion_r2781484939)).
Fixes `Buffer.slice()` / `Buffer.subarray()` on resizable `ArrayBuffer`
/ growable `SharedArrayBuffer` to return a **fixed-length view** instead
of a length-tracking view.
## Problem
The resizable/growable branch was passing `std::nullopt` to
`JSUint8Array::create()`, which creates a length-tracking view. When the
underlying buffer grows, the sliced view's length would incorrectly
expand:
```js
const rab = new ArrayBuffer(10, { maxByteLength: 20 });
const buf = Buffer.from(rab);
const sliced = buf.slice(0, 5);
sliced.length; // 5
rab.resize(20);
sliced.length; // was 10 (wrong), now 5 (correct)
```
Node.js specifies that `Buffer.slice()` always returns a fixed-length
view (verified on Node.js v22).
## Fix
Replace `std::nullopt` with `newLength` in the
`isResizableOrGrowableShared()` branch of
`jsBufferPrototypeFunction_sliceBody`.
## Test
Added a regression test that creates a `Buffer` from a resizable
`ArrayBuffer`, slices it, resizes the buffer, and verifies the slice
length doesn't change.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
### What does this PR do?
Instead of calling event_loop.wakeup() (which writes to the eventfd)
when there are pending immediate tasks, use a zero timeout in
getTimeout() so epoll/kqueue returns immediately. This avoids the
overhead of the eventfd write/read cycle on each setImmediate iteration.
On Windows, continue to call .wakeup() since that's cheap for libuv.
Verified with strace: system bun makes ~44k eventfd writes for a 5s
setImmediate loop, while this change makes 0.
### How did you verify your code works?
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
Fixes the remaining kqueue filter comparison bug in
`packages/bun-usockets/src/eventing/epoll_kqueue.c` that caused
excessive CPU usage with network requests on macOS:
- **`us_loop_run_bun_tick` filter comparison (line 302-303):** kqueue
filter values (`EVFILT_READ=-1`, `EVFILT_WRITE=-2`) were compared using
bitwise AND (`&`) instead of equality (`==`). Since these are signed
negative integers (not bitmasks), `(-2) & (-1)` = `-2` (truthy), meaning
every `EVFILT_WRITE` event was also misidentified as `EVFILT_READ`. This
was already fixed in `us_loop_run` (by PR #25475) but the same bug
remained in `us_loop_run_bun_tick`, which is the primary event loop
function used by Bun.
This is a macOS-only issue (Linux uses epoll, which is unaffected).
Closes#26811
## Test plan
- [x] Added regression test at `test/regression/issue/26811.test.ts`
that makes concurrent HTTPS POST requests
- [x] Test passes with `bun bd test test/regression/issue/26811.test.ts`
- [ ] Manual verification on macOS: run the reporter's [repro
script](https://gist.github.com/jkoppel/d26732574dfcdcc6bfc4958596054d2e)
and confirm CPU usage stays low
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
- Switch both eventfd wakeup sites (Zig IO watcher loop and usockets
async) to edge-triggered (`EPOLLET`) epoll mode, eliminating unnecessary
`read()` syscalls on every event loop wakeup
- Add `EAGAIN`/`EINTR` overflow handling in `us_internal_async_wakeup`,
matching libuv's approach ([commit
`e5cb1d3d`](https://github.com/libuv/libuv/commit/e5cb1d3d))
With edge-triggered mode, each `write()` to the eventfd produces a new
edge event regardless of the current counter value, so draining the
counter via `read()` is unnecessary. The counter will never overflow in
practice (~18 quintillion wakeups), but overflow handling is included
defensively.
### Files changed
- **`src/io/io.zig`** — Add `EPOLL.ET` to eventfd registration, replace
drain `read()` with `continue`
- **`packages/bun-usockets/src/eventing/epoll_kqueue.c`** — Set
`leave_poll_ready = 1` for async callbacks, upgrade to `EPOLLET` via
`EPOLL_CTL_MOD`, add `EAGAIN`/`EINTR` handling in wakeup write
## Test plan
- [x] Verified with `strace -f -e trace=read,eventfd2` that eventfd
reads are fully eliminated after the change (0 reads on the eventfd fd)
- [x] Confirmed remaining 8-byte reads in traces are timerfd reads
(legitimate, required)
- [x] Stress tested with 50 concurrent async tasks (1000 total
`Bun.sleep(1)` iterations) — all completed correctly
- [x] `LinuxWaker.wait()` (used by `BundleThread` as a blocking sleep)
is intentionally unchanged
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
Add a fast path for `structuredClone` and `postMessage` when the root
value is a dense array of primitives or strings. This bypasses the full
`CloneSerializer`/`CloneDeserializer` machinery by keeping data in
native C++ structures instead of serializing to a byte stream.
**Important:** This optimization only applies when the root value passed
to `structuredClone()` / `postMessage()` is an array. Nested arrays
within objects still go through the normal serialization path.
## Implementation
Three tiers of array fast paths, checked in order:
| Tier | Indexing Type | Strategy | Applies When |
|------|--------------|----------|--------------|
| **Tier 1** | `ArrayWithInt32` | `memcpy` butterfly data | Dense int32
array, no holes, no named properties |
| **Tier 2** | `ArrayWithDouble` | `memcpy` butterfly data | Dense
double array, no holes, no named properties |
| **Tier 3** | `ArrayWithContiguous` | Copy elements into
`FixedVector<variant<JSValue, String>>` | Dense array of
primitives/strings, no holes, no named properties |
All tiers fall through to the normal serialization path when:
- The array has holes that must forward to the prototype
- The array has named properties (e.g., `arr.foo = "bar"`) — checked via
`structure->maxOffset() != invalidOffset`
- Elements contain non-primitive, non-string values (objects, arrays,
etc.)
- The context requires wire-format serialization (storage, cross-process
transfer)
### Deserialization
- **Tier 1/2:** Allocate a new `Butterfly` via `vm.auxiliarySpace()`,
`memcpy` data back, create array with `JSArray::createWithButterfly()`.
Falls back to normal deserialization if `isHavingABadTime` (forced
ArrayStorage mode).
- **Tier 3:** Pre-convert elements to `JSValue` (including `jsString()`
allocation), then use `JSArray::tryCreateUninitializedRestricted()` +
`initializeIndex()`.
## Benchmarks
Apple M4 Max, comparing system Bun 1.3.8 vs this branch (release build):
| Benchmark | Before | After | Speedup |
|-----------|--------|-------|---------|
| `structuredClone([10 numbers])` | 308.71 ns | 40.38 ns | **7.6x** |
| `structuredClone([100 numbers])` | 1.62 µs | 86.87 ns | **18.7x** |
| `structuredClone([1000 numbers])` | 13.79 µs | 544.56 ns | **25.3x** |
| `structuredClone([10 strings])` | 642.38 ns | 307.38 ns | **2.1x** |
| `structuredClone([100 strings])` | 5.67 µs | 2.57 µs | **2.2x** |
| `structuredClone([10 mixed])` | 446.32 ns | 198.35 ns | **2.3x** |
| `structuredClone(nested array)` | 1.84 µs | 1.79 µs | 1.0x (not
eligible) |
| `structuredClone({a: 123})` | 95.98 ns | 100.07 ns | 1.0x (no
regression) |
Int32 arrays see the largest gains (up to 25x) since they use a direct
`memcpy` of butterfly memory. String/mixed arrays see ~2x improvement.
No performance regression on non-eligible inputs.
## Bug Fix
Also fixes a correctness bug where arrays with named properties (e.g.,
`arr.foo = "bar"`) would lose those properties when going through the
array fast path. Added a `structure->maxOffset() != invalidOffset` guard
to fall back to normal serialization for such arrays.
Fixed a minor double-counting issue in `computeMemoryCost` where
`JSValue` elements in `SimpleArray` were counted both by `byteSize()`
and individually.
## Test Plan
38 tests in `test/js/web/structured-clone-fastpath.test.ts` covering:
- Basic array types: empty, numbers, strings, mixed primitives, special
numbers (`-0`, `NaN`, `Infinity`)
- Large arrays (10,000 elements)
- Tier 2: double arrays, Int32→Double transition
- Deep clone independence verification
- Named properties on Int32, Double, and Contiguous arrays
- `postMessage` via `MessageChannel` for Int32, Double, and mixed arrays
- Edge cases: frozen/sealed arrays, deleted elements (holes), `length`
extension, single-element arrays
- Prototype modification (custom prototype, indexed prototype properties
with holes)
- `Array` subclass identity loss (per spec)
- `undefined`-only and `null`-only arrays
- Multiple independent clones from the same source
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Add `minify.unwrapCJSToESM` JS API option and `--unwrap-cjs-to-esm` CLI
flag to force CJS-to-ESM conversion for specific packages, eliminating
the `__commonJS` wrapper. Supports wildcard patterns (e.g. `"@scope/*"`).
User entries extend the default React family list.
Also removes the react/react-dom version check that gated conversion,
and fixes `packageName()` to handle scoped packages (`@scope/pkg`).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
- Fix crash ("Pure virtual function called!") when WebSocket client
receives binary data with `binaryType = "blob"` and no event listener
attached
- Add missing `incPendingActivityCount()` call before `postTask` in the
Blob case of `didReceiveBinaryData`
- Add regression test for issue #26669
## Root Cause
The Blob case in `didReceiveBinaryData` (WebSocket.cpp:1324-1331) was
calling `decPendingActivityCount()` inside the `postTask` callback
without a matching `incPendingActivityCount()` beforehand. This bug was
introduced in #21471 when Blob support was added.
The ArrayBuffer and NodeBuffer cases correctly call
`incPendingActivityCount()` before `postTask`, but the Blob case was
missing this call.
## Test plan
- [x] New regression test verifies WebSocket with `binaryType = "blob"`
doesn't crash on ping frames
- [x] `bun bd test test/regression/issue/26669.test.ts` passes
Fixes#26669🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Ciro Spaciari MacBook <ciro@anthropic.com>
## Summary
`Bun.stringWidth` was incorrectly treating Thai SARA AA (U+0E32), SARA
AM (U+0E33), and their Lao equivalents (U+0EB2, U+0EB3) as zero-width
characters.
## Root Cause
In `src/string/immutable/visible.zig`, the range check for Thai/Lao
combining marks was too broad:
- Thai: `0xe31 <= cp <= 0xe3a` included U+0E32 and U+0E33
- Lao: `0xeb1 <= cp <= 0xebc` included U+0EB2 and U+0EB3
According to Unicode (UCD Grapheme_Break property), these are **spacing
vowels** (Grapheme_Base), not combining marks.
## Changes
- **`src/string/immutable/visible.zig`**: Exclude U+0E32, U+0E33,
U+0EB2, U+0EB3 from zero-width ranges
- **`test/js/bun/util/stringWidth.test.ts`**: Add tests for Thai and Lao
spacing vowels
## Before/After
| Character | Before | After |
|-----------|--------|-------|
| `\u0E32` (SARA AA) | 0 | 1 |
| `\u0E33` (SARA AM) | 0 | 1 |
| `คำ` (common Thai word) | 1 | 2 |
| `\u0EB2` (Lao AA) | 0 | 1 |
| `\u0EB3` (Lao AM) | 0 | 1 |
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…ments (#26717)"
This reverts commit 315e822866.
### What does this PR do?
### How did you verify your code works?
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### What does this PR do?
Enables the `net.Server → Http2SecureServer` connection upgrade pattern
used by libraries like
[http2-wrapper](https://github.com/szmarczak/http2-wrapper),
[crawlee](https://github.com/apify/crawlee), and custom HTTP/2 proxy
servers. This pattern works by accepting raw TCP connections on a
`net.Server` and forwarding them to an `Http2SecureServer` via
`h2Server.emit('connection', rawSocket)`.
#### Bug fixes
**SSLWrapper use-after-free (Zig)**
Two use-after-free bugs in `ssl_wrapper.zig` are fixed:
1. **`flush()` stale pointer** — `flush()` captured the `ssl` pointer
*before* calling `handleTraffic()`, which can trigger a close callback
that frees the SSL object via `deinit`. The pointer was then used after
being freed. Fix: read `this.ssl` *after* `handleTraffic()` returns.
2. **`handleReading()` null dereference** — `handleReading()` called
`triggerCloseCallback()` after `triggerDataCallback()` without checking
whether the data callback had already closed the connection. This led to
a null function pointer dereference. Fix: check `this.ssl == null ||
this.flags.closed_notified` before calling the close callback.
### How did you verify your code works?
- Added **13 in-process tests** (`node-http2-upgrade.test.mts`) covering
the `net.Server → Http2SecureServer` upgrade path:
- GET/POST requests through upgraded connections
- Sequential requests sharing a single H2 session
- `session` event emission
- Concurrent clients with independent sessions
- Socket close ordering (rawSocket first vs session first) — no crash
- ALPN protocol negotiation (`h2`)
- Varied status codes (200, 302, 404)
- Client disconnect mid-response (stream destroyed early)
- Three independent clients producing three distinct sessions
- Tests use `node:test` + `node:assert` and **pass in both Bun and
Node.js**
- Ported `test-http2-socket-close.js` from the Node.js test suite,
verifying no segfault when the raw socket is destroyed before the H2
session is closed
---------
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
Fixes a bug where sequential HTTP requests with proxy-style absolute
URLs (e.g. `GET http://example.com/path HTTP/1.1`) hang on the 2nd+
request when using keep-alive connections.
## Root Cause
In `packages/bun-uws/src/HttpParser.h`, the parser was treating
proxy-style absolute URLs identically to `CONNECT` method requests —
setting `isConnectRequest = true` and entering tunnel mode. This flag
was never reset between requests on the same keep-alive connection, so
the 2nd+ request was swallowed as raw tunnel data instead of being
parsed as HTTP.
## Fix
3-line change in `HttpParser.h:569`:
- **`isConnect`**: Now only matches actual `CONNECT` method requests
(removed `isHTTPorHTTPSPrefixForProxies` from the condition)
- **`isProxyStyleURL`**: New variable that detects `http://`/`https://`
prefixes and accepts them as valid request targets — without triggering
tunnel mode
## Who was affected
- Any Bun HTTP server (`Bun.serve()` or `node:http createServer`)
receiving proxy-style requests on keep-alive connections
- HTTP proxy servers built with Bun could only handle one request per
connection
- Bun's own HTTP client making sequential requests through an HTTP proxy
backed by a Bun server
## Test
Added `test/js/node/http/node-http-proxy-url.test.ts` with 3 test cases:
1. Sequential GET requests with absolute URL paths
2. Sequential POST requests with absolute URL paths
3. Mixed normal and proxy-style URLs
Tests run under both Node.js and Bun for compatibility verification.
- ❌ Fails with system bun (2/3 tests timeout on 2nd request)
- ✅ Passes with debug build (3/3 tests pass)
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
- Fix a use-after-free bug in the bindgen code generator where string
arguments with default values would have their underlying WTF::String
destroyed before the BunString was used
- The issue occurred because for optional string parameters with
defaults, a WTF::String was created inside an `if` block, converted to
BunString, then the if block closed and destroyed the WTF::String while
the BunString was still in use
- This manifested as a segfault in `Bun.stringWidth()` and potentially
other functions using optional string arguments
## Details
The crash stack trace showed:
```
Segmentation fault at address 0x31244B0F0
visible.zig:888: string.immutable.visible.visible.visibleUTF16WidthFn
BunObject.zig:1371: bindgen_BunObject_dispatchStringWidth1
GeneratedBindings.cpp:242: bindgen_BunObject_jsStringWidth
```
The generated code before this fix looked like:
```cpp
BunString argStr;
if (!arg0.value().isUndefinedOrNull()) {
WTF::String wtfString_0 = WebCore::convert<...>(...);
argStr = Bun::toString(wtfString_0);
} // <-- wtfString_0 destroyed here!
// ... argStr used later, pointing to freed memory
```
The fix declares the WTF::String holder outside the if block:
```cpp
BunString argStr;
WTF::String wtfStringHolder_0; // Lives until function returns
if (!arg0.value().isUndefinedOrNull()) {
wtfStringHolder_0 = WebCore::convert<...>(...);
}
if (!wtfStringHolder_0.isEmpty()) argStr = Bun::toString(wtfStringHolder_0);
// argStr now points to valid memory
```
This fix applies to both:
- Direct string function arguments with defaults (e.g.,
`t.DOMString.default("")`)
- Dictionary fields with string defaults
## Test plan
- [x] Existing `stringWidth.test.ts` tests pass (105 tests)
- [x] Manual testing with GC stress shows no crashes
- [x] `os.userInfo()` with encoding option works correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Update LLVM version references across build scripts, Dockerfiles, CI,
Nix configs, and documentation
- Fix LLVM 21 `-Wcharacter-conversion` errors in WebKit bindings:
- `EncodingTables.h`: pragma for intentional char32_t/char16_t
comparisons
- `TextCodecCJK.cpp`: widen `gb18030AsymmetricEncode` param to char32_t
- `URLPatternParser`: widen `isValidNameCodepoint` param to char32_t,
cast for `startsWith`
- Fix `__libcpp_verbose_abort` noexcept mismatch (LLVM 21 uses
`_NOEXCEPT`)
- Fix dangling pointer in `BunJSCModule.h` (`toCString` temporary
lifetime)
- Remove `useMathSumPreciseMethod` (removed upstream in JSC)
**Before merging:** Merge https://github.com/oven-sh/WebKit/pull/153
first, then update `WEBKIT_VERSION` in `cmake/tools/SetupWebKit.cmake`
to point to the merged commit.
## Test plan
- [ ] Build bun debug on macOS with LLVM 21
- [ ] Build bun on Linux (glibc)
- [ ] Build bun on Linux (musl)
- [ ] Build bun on Windows
- [ ] Run test suite
Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
- Add `[Symbol.dispose]` to mock function prototype, aliased to
`mockRestore`
- Enables `using spy = spyOn(obj, "method")` to auto-restore when
leaving scope
- Works for both `spyOn()` and `mock()`
Addresses #6040 — gives users a clean way to scope spy lifetimes instead
of manually calling `mockRestore()` or relying on `afterEach`.
### Example
```ts
import { spyOn, expect, test } from "bun:test";
test("auto-restores spy", () => {
const obj = { method: () => "original" };
{
using spy = spyOn(obj, "method").mockReturnValue("mocked");
expect(obj.method()).toBe("mocked");
}
// automatically restored
expect(obj.method()).toBe("original");
});
```
## Test plan
- `bun bd test test/js/bun/test/mock-disposable.test.ts` — 3 tests pass
- Verified tests fail with `USE_SYSTEM_BUN=1`
## Summary
- Make `defaultRemainingRunsUntilSkipReleaseAccess` configurable at
runtime instead of a compile-time constant
- Add `BUN_GC_RUNS_UNTIL_SKIP_RELEASE_ACCESS` environment variable to
control how many idle event loop iterations pass before skipping JSC
heap `releaseAccess` calls in `onBeforeWait`
- Default remains 10, matching the previous hardcoded value
## Test plan
- [ ] Verify default behavior is unchanged (no env var set, value is 10)
- [ ] Verify `BUN_GC_RUNS_UNTIL_SKIP_RELEASE_ACCESS=0` causes release
access to be skipped every iteration
- [ ] Verify `BUN_GC_RUNS_UNTIL_SKIP_RELEASE_ACCESS=100` delays skipping
for 100 idle iterations
- [ ] Verify negative values are ignored (default is preserved)
- [ ] Verify non-numeric values are ignored (default is preserved)
## Changelog
<!-- CHANGELOG:START -->
<!-- CHANGELOG:END -->
🤖 Generated with [Claude Code](https://claude.com/claude-code) (0%
3-shotted by claude)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
This is a tiny optimization that skips creating and dispatching an Event
object when `AbortSignal.abort()` is called with no registered
listeners.
## Changes
When there are no listeners (no `addEventListener` or `onabort`), we now
check `hasEventListeners()` before creating the Event, avoiding:
- Event object allocation (~112 bytes)
- EventPath creation
- dispatchEvent overhead (hash map lookups, method calls)
## Performance
Improvement for the no-listener case:
- **~6% faster** in micro-benchmarks
- ~16ms saved per 1M `abort()` calls (271ms → 255ms)
| Case | Before | After | Improvement |
|------|--------|-------|-------------|
| no listener | 271 ms | 255 ms | ~6% |
| with listener | 368 ms | 370 ms | (same) |
## Why this is safe
The optimization has no observable side effects because:
- `dispatchEvent` is called from C++, not observable via JS
monkey-patching
- Without listeners, no code can obtain a reference to the Event object
- All internal state (`aborted`, `reason`) is set correctly regardless
## Test Plan
- Existing AbortController/AbortSignal tests pass
- Added mitata benchmark: `bench/snippets/abort-signal.mjs`
## Summary
- Fix path normalization for "." on Windows where `normalizeStringBuf`
was incorrectly stripping it to an empty string
- This caused `existsSync('.')`, `statSync('.')`, and other fs
operations to fail on Windows
## Test plan
- Added regression test `test/regression/issue/26631.test.ts` that tests
`existsSync`, `exists`, `statSync`, and `stat` for both `.` and `..`
paths
- All tests pass locally with `bun bd test
test/regression/issue/26631.test.ts`
- Verified code compiles on all platforms with `bun run zig:check-all`
Fixes#26631🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Add validation to require `--compile` when using ESM bytecode
- Update documentation to clarify ESM bytecode requirements
## Why
ESM module resolution is two-phase: (1) analyze imports/exports, (2)
evaluate. Without `--compile`, there's no `module_info` embedded, so JSC
must still parse the file for module analysis even with bytecode -
causing a double-parse deopt.
## Changes
- **CLI**: Error when `--bytecode --format=esm` is used without
`--compile`
- **JS API**: Error when `bytecode: true, format: 'esm'` is used without
`compile: true`
- **Docs**: Update bytecode.mdx, executables.mdx, index.mdx to clarify
requirements
- **Types**: Update JSDoc for bytecode option in bun.d.ts
## Test plan
```bash
# Should error
bun build ./test.js --bytecode --format=esm --outdir=./out
# error: ESM bytecode requires --compile. Use --format=cjs for bytecode without --compile.
# Should work
bun build ./test.js --bytecode --format=esm --compile --outfile=./mytest
bun build ./test.js --bytecode --format=cjs --outdir=./out
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Adds `--cpu-prof-interval` to configure the CPU profiler sampling
interval in microseconds (default: 1000), matching Node.js's
`--cpu-prof-interval` flag.
```sh
bun --cpu-prof --cpu-prof-interval 500 index.js
```
- Parsed as `u32`, truncated to `c_int` when passed to JSC's
`SamplingProfiler::setTimingInterval`
- Invalid values silently fall back to the default (1000μs)
- Warns if used without `--cpu-prof` or `--cpu-prof-md`
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
Fixes#26625
This fixes a segmentation fault that occurred on Windows x64 when the GC
finalizer tried to free shell interpreter resources that were already
partially freed during normal shell completion.
- Added explicit `cleanup_state` enum to track resource ownership state
- `needs_full_cleanup`: Nothing cleaned up yet, finalizer must clean
everything
- `runtime_cleaned`: `finish()` already cleaned IO/shell, finalizer
skips those
- Early return in `#derefRootShellAndIOIfNeeded()` when already cleaned
- Explicit state-based cleanup in `deinitFromFinalizer()`
The vulnerability existed on all platforms but was most reliably
triggered on Windows with high GC pressure (many concurrent shell
commands).
## Test plan
- [x] Build passes (`bun bd`)
- [x] New regression test added (`test/regression/issue/26625.test.ts`)
- [x] Existing shell tests pass (same 4 pre-existing failures, no new
failures)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
- `bun run build:local` now handles everything: configuring JSC,
building JSC, and building Bun in a single command on all platforms
(macOS, Linux, Windows). Previously required manually running `bun run
jsc:build:debug`, deleting a duplicate `InspectorProtocolObjects.h`
header, and then running the Bun build separately.
- Incremental JSC rebuilds: JSC is built via `add_custom_target` that
delegates to JSC's inner Ninja, which tracks WebKit source file changes
and only rebuilds what changed. `ninja -Cbuild/debug-local` also works
after the first build.
- Cross-platform support:
- macOS: Uses system ICU automatically
- Linux: Uses system ICU via find_package instead of requiring bundled
static libs
- Windows: Builds ICU from source automatically (only when libs don't
already exist), sets up static CRT and ICU naming conventions
### Changes
- cmake/tools/SetupWebKit.cmake: Replace the old WEBKIT_LOCAL block
(which just set include paths and assumed JSC was pre-built) with full
JSC configure + build integration for all platforms
- cmake/targets/BuildBun.cmake: Add jsc as a build dependency, use
system ICU on Linux for local builds, handle bmalloc linking for local
builds
- CONTRIBUTING.md / docs/project/contributing.mdx: Simplify "Building
WebKit locally" docs from ~15 lines of manual steps to 3 lines
## Test plan
- [x] macOS arm64: clean build, incremental rebuild, WebKit source
change rebuild
- [x] Windows x64: clean build with ICU, incremental rebuild with ICU
skip
- [x] Linux x64: build with system ICU via find_package
- [x] No duplicate InspectorProtocolObjects.h errors
- [x] build/debug-local/bun-debug --version works
Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
### What does this PR do?
Extract NO_PROXY checking logic from getHttpProxyFor into a reusable
isNoProxy method on the env Loader. This allows both fetch() and
WebSocket to check NO_PROXY even when a proxy is explicitly provided via
the proxy option (not just via http_proxy env var).
Changes:
- env_loader.zig: Extract isNoProxy() from getHttpProxyFor()
- FetchTasklet.zig: Check isNoProxy() before using explicit proxy
- WebSocket.cpp: Check Bun__isNoProxy() before using explicit proxy
- virtual_machine_exports.zig: Export Bun__isNoProxy for C++ access
- Add NO_PROXY tests for both fetch and WebSocket proxy paths
### How did you verify your code works?
Tests
---------
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
## Summary
- Fix type definition for `Socket.reload()` to match runtime behavior
- The runtime expects `{ socket: handler }` but types previously
accepted just `handler`
## Test plan
- [x] Added regression test `test/regression/issue/26290.test.ts`
- [x] Verified test passes with `bun bd test`
Fixes#26290🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Alistair Smith <hi@alistair.sh>
## Summary
- Adds missing SIMD variants to the `Build.Target` TypeScript type
- The runtime accepts targets like `bun-linux-x64-modern` but TypeScript
was rejecting them
- Generalized the type to use `${Architecture}` template where possible
## Test plan
- [x] Added regression test in `test/regression/issue/26247.test.ts`
that validates all valid target combinations type-check correctly
- [x] Verified with `bun bd test test/regression/issue/26247.test.ts`
Fixes#26247🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Alistair Smith <hi@alistair.sh>
## Summary
- Adds missing `bun-linux-x64-baseline` and `bun-linux-x64-modern`
compile target types
- These targets are supported by the Bun CLI but were missing from the
TypeScript type definitions
## Changes
Added `bun-linux-x64-${SIMD}` to the `CompileTarget` type union, which
expands to:
- `bun-linux-x64-baseline`
- `bun-linux-x64-modern`
## Test plan
- [x] TypeScript should now accept `target: 'bun-linux-x64-modern'`
without type errors
Closes#26247🤖 Generated with [Claude Code](https://claude.com/claude-code)
### What does this PR do?
### How did you verify your code works?
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
## Summary
- Improve handling of fragmented chunk data in the HTTP parser
- Add test coverage for edge cases
## Test plan
- [x] New tests pass
- [x] Existing tests pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
### What does this PR do?
fixes https://github.com/oven-sh/bun/issues/26597
### How did you verify your code works?
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
Add a CI step that runs JSC JIT stress tests under QEMU when
`SetupWebKit.cmake` is modified. This complements #26571 (basic baseline
CPU verification) by also testing JIT-generated code.
## Motivation
PR #26571 added QEMU-based verification that catches illegal
instructions in:
- Startup code
- Static initialization
- Basic interpreter execution
However, JIT compilers (DFG, FTL, Wasm BBQ/OMG) generate code at runtime
that could emit AVX or LSE instructions even if the compiled binary
doesn't. The JSC stress tests from #26380 exercise all JIT tiers through
hot loops that trigger tier-up.
## How it works
1. Detects if `cmake/tools/SetupWebKit.cmake` is modified in the PR
2. If WebKit changes are detected, runs `verify-jit-stress-qemu.sh`
after the build
3. Executes all 78 JIT stress test fixtures under QEMU with restricted
CPU features:
- x64: `qemu-x86_64 -cpu Nehalem` (SSE4.2, no AVX)
- aarch64: `qemu-aarch64 -cpu cortex-a53` (ARMv8.0-A, no LSE)
4. Any SIGILL from JIT-generated code fails the build
## Platforms tested
| Target | CPU Model | What it catches |
|---|---|---|
| `linux-x64-baseline` | Nehalem | JIT emitting AVX/AVX2/AVX512 |
| `linux-x64-musl-baseline` | Nehalem | JIT emitting AVX/AVX2/AVX512 |
| `linux-aarch64` | Cortex-A53 | JIT emitting LSE atomics, SVE |
| `linux-aarch64-musl` | Cortex-A53 | JIT emitting LSE atomics, SVE |
## Timeout
The step has a 30-minute timeout since QEMU emulation is ~10-50x slower
than native. This only runs on WebKit update PRs, so it won't affect
most CI runs.
## Refs
- #26380 - Added JSC JIT stress tests
- #26571 - Added basic QEMU baseline verification
## Summary
Adds `bun run --parallel` and `bun run --sequential` — new flags for
running multiple package.json scripts concurrently or sequentially with
Foreman-style prefixed output. Includes full `--filter`/`--workspaces`
integration for running scripts across workspace packages.
### Usage
```bash
# Run "build" and "test" concurrently from the current package.json
bun run --parallel build test
# Run "build" and "test" sequentially with prefixed output
bun run --sequential build test
# Glob-matched script names
bun run --parallel "build:*"
# Run "build" in all workspace packages concurrently
bun run --parallel --filter '*' build
# Run "build" in all workspace packages sequentially
bun run --sequential --workspaces build
# Glob-matched scripts across all packages
bun run --parallel --filter '*' "build:*"
# Multiple scripts across all packages
bun run --parallel --filter '*' build lint test
# Continue running even if one package fails
bun run --parallel --no-exit-on-error --filter '*' test
# Skip packages missing the script
bun run --parallel --workspaces --if-present build
```
## How it works
### Output format
Each script's stdout/stderr is prefixed with a colored, padded label:
```
build | compiling...
test | running suite...
lint | checking files...
```
### Label format
- **Without `--filter`/`--workspaces`**: labels are just the script name
→ `build | output`
- **With `--filter`/`--workspaces`**: labels are `package:script` →
`pkg-a:build | output`
- **Fallback**: if a package.json has no `name` field, the relative path
from the workspace root is used (e.g., `packages/my-pkg:build`)
### Execution model
- **`--parallel`**: all scripts start immediately, output is interleaved
with prefixes
- **`--sequential`**: scripts run one at a time in order, each waiting
for the previous to finish
- **Pre/post scripts** (`prebuild`/`postbuild`) are grouped with their
main script and run in dependency order within each group
- By default, a failure kills all remaining scripts.
`--no-exit-on-error` lets all scripts finish.
### Workspace integration
The workspace branch in `multi_run.zig` uses a two-pass approach for
deterministic ordering:
1. **Collect**: iterate workspace packages using
`FilterArg.PackageFilterIterator` (same infrastructure as
`filter_run.zig`), filtering with `FilterArg.FilterSet`, collecting
matched packages with their scripts, PATH, and cwd.
2. **Sort**: sort matched packages by name (tiebreak by directory path)
for deterministic ordering — filesystem iteration order from the glob
walker is nondeterministic.
3. **Build configs**: for each sorted package, expand script names
(including globs like `build:*`) against that package's scripts map,
creating `ScriptConfig` entries with `pkg:script` labels and per-package
cwd/PATH.
### Behavioral consistency with `filter_run.zig`
| Behavior | `filter_run.zig` | `multi_run.zig` (this PR) |
|----------|-------------------|---------------------------|
| `--workspaces` skips root package | Yes | Yes |
| `--workspaces` errors on missing script | Yes | Yes |
| `--if-present` silently skips missing | Yes | Yes |
| `--filter` without `--workspaces` includes root | Yes (if matches) |
Yes (if matches) |
| Pre/post script chains | Per-package | Per-package |
| Per-package cwd | Yes | Yes |
| Per-package PATH (`node_modules/.bin`) | Yes | Yes |
### Key implementation details
- Each workspace package script runs in its own package directory with
its own `node_modules/.bin` PATH
- `dirpath` from the glob walker is duped to avoid use-after-free when
the iterator's arena is freed between patterns
- `addScriptConfigs` takes an optional `label_prefix` parameter — `null`
for single-package mode, package name for workspace mode
- `MultiRunProcessHandle` is registered in the `ProcessExitHandler`
tagged pointer union in `process.zig`
## Files changed
| File | Change |
|------|--------|
| `src/cli/multi_run.zig` | New file: process management, output
routing, workspace integration, dependency ordering |
| `src/cli.zig` | Dispatch to `MultiRun.run()` for
`--parallel`/`--sequential`, new context fields |
| `src/cli/Arguments.zig` | Parse `--parallel`, `--sequential`,
`--no-exit-on-error` flags |
| `src/bun.js/api/bun/process.zig` | Register `MultiRunProcessHandle` in
`ProcessExitHandler` tagged pointer union |
| `test/cli/run/multi-run.test.ts` | 118 tests (102 core + 16 workspace
integration) |
| `docs/pm/filter.mdx` | Document `--parallel`/`--sequential` +
`--filter`/`--workspaces` combination |
| `docs/snippets/cli/run.mdx` | Add `--parallel`, `--sequential`,
`--no-exit-on-error` parameter docs |
## Test plan
All 118 tests pass with debug build (`bun bd test
test/cli/run/multi-run.test.ts`). The 16 new workspace tests all fail
with system bun (`USE_SYSTEM_BUN=1`), confirming they test new
functionality.
### Workspace integration tests (16 tests)
1. `--parallel --filter='*'` runs script in all packages
2. `--parallel --filter='pkg-a'` runs only in matching package
3. `--parallel --workspaces` matches all workspace packages
4. `--parallel --filter='*'` with glob expands per-package scripts
5. `--sequential --filter='*'` runs in sequence (deterministic order)
6. Workspace + failure aborts other scripts
7. Workspace + `--no-exit-on-error` lets all finish
8. `--workspaces` skips root package
9. Each workspace script runs in its own package directory (cwd
verification)
10. Multiple script names across workspaces (`build` + `test`)
11. Pre/post scripts work per workspace package
12. `--filter` skips packages without the script (no error)
13. `--workspaces` errors when a package is missing the script
14. `--workspaces --if-present` skips missing scripts silently
15. Labels are padded correctly across workspace packages
16. Package without `name` field uses relative path as label
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Fixes illegal instruction (SIGILL) crashes on ARMv8.0 aarch64 CPUs
(Cortex-A53, Raspberry Pi 4, AWS a1 instances).
## Root cause
Upstream mimalloc force-enables `MI_OPT_ARCH` on arm64, which adds
`-march=armv8.1-a` and emits LSE atomic instructions (`casa`, `swpa`,
`ldaddl`). These are not available on ARMv8.0 CPUs.
## Fix
- Pass `MI_NO_OPT_ARCH=ON` to mimalloc on aarch64 (has priority over
`MI_OPT_ARCH` in mimalloc's CMake)
- Update WebKit to autobuild-596e48e22e3a1090e5b802744a7938088b1ea860
which explicitly passes `-march` flags to the WebKit build
## Verification
Includes QEMU-based baseline CPU verification CI steps (#26571) that
catch these regressions automatically.
## Summary
Add CI steps that verify baseline builds don't use CPU instructions
beyond their target. Uses QEMU user-mode emulation with restricted CPU
features — any illegal instruction causes SIGILL and fails the build.
## Platforms verified
| Build Target | QEMU Command | What it catches |
|---|---|---|
| `linux-x64-baseline` (glibc) | `qemu-x86_64 -cpu Nehalem` | AVX, AVX2,
AVX512 |
| `linux-x64-musl-baseline` | `qemu-x86_64 -cpu Nehalem` | AVX, AVX2,
AVX512 |
| `linux-aarch64` (glibc) | `qemu-aarch64 -cpu cortex-a35` | LSE
atomics, SVE, dotprod |
| `linux-aarch64-musl` | `qemu-aarch64 -cpu cortex-a35` | LSE atomics,
SVE, dotprod |
## How it works
Each verify step:
1. Downloads the built binary artifact from the `build-bun` step
2. Installs `qemu-user-static` on-the-fly (dnf/apk/apt-get)
3. Runs two smoke tests under QEMU with restricted CPU features:
- `bun --version` — validates startup, linker, static init code
- `bun -e eval` — validates JSC initialization and basic execution
4. Hard fails on SIGILL (exit code 132)
The verify step runs in the build group after `build-bun`, with a
5-minute timeout.
## Known issue this will surface
**mimalloc on aarch64**: Built with `MI_OPT_ARCH=ON` which adds
`-march=armv8.1-a`, enabling LSE atomics. This will SIGILL on
Cortex-A35/A53 CPUs. The aarch64 verify steps are expected to fail
initially, confirming the test catches real issues. Fix can be done
separately in `cmake/targets/BuildMimalloc.cmake`.
When a `SyntheticModule` callback was wrapped in an `AsyncContextFrame`
on the main globalObject (where async context tracking is enabled),
evaluating it on a `NodeVMGlobalObject` would crash because the tracking
flag wasn't propagated.
`AsyncContextFrame::call` checks `isAsyncContextTrackingEnabled()` to
decide whether to unwrap the frame — without the flag, it takes the fast
path and tries to call the `AsyncContextFrame` wrapper directly, which
is not callable.
The async context data (`m_asyncContextData`) was already shared between
parent and `NodeVMGlobalObject`, but the tracking flag was missing. This
adds propagation of `isAsyncContextTrackingEnabled` alongside the data.
**Repro:** `react-email` v5.2.5 preview server crashes when rendering a
template because it imports `node:async_hooks` (enabling async context
tracking) and uses `node:vm` `SyntheticModule` for module evaluation.
Fixes#26540
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
- Adds `--metafile-md` CLI option to `bun build` that generates a
markdown visualization of the module graph
- Designed to help Claude and other LLMs analyze bundle composition,
identify bloat, and understand dependency chains
- Reuses existing metafile JSON generation code as a post-processing
step
## Features
The generated markdown includes:
1. **Quick Summary** - Module counts, sizes, ESM/CJS breakdown,
output/input ratio
2. **Largest Input Files** - Sorted by size to identify potential bloat
3. **Entry Point Analysis** - Shows bundle size, exports, CSS bundles,
and bundled modules
4. **Dependency Chains** - Most commonly imported modules and reverse
dependencies
5. **Full Module Graph** - Complete import/export info for each module
6. **Raw Data for Searching** - Grep-friendly markers in code blocks:
- `[MODULE:]`, `[SIZE:]`, `[IMPORT:]`, `[IMPORTED_BY:]`
- `[ENTRY:]`, `[EXTERNAL:]`, `[NODE_MODULES:]`
## Usage
```bash
# Default filename (meta.md)
bun build entry.js --metafile-md --outdir=dist
# Custom filename
bun build entry.js --metafile-md=analysis.md --outdir=dist
# Both JSON and markdown
bun build entry.js --metafile=meta.json --metafile-md=meta.md --outdir=dist
```
## Example Output
See sample output: https://gist.github.com/example (will add)
## Test plan
- [x] Test default filename (`meta.md`)
- [x] Test custom filename
- [x] Test both `--metafile` and `--metafile-md` together
- [x] Test summary metrics
- [x] Test module format info (ESM/CJS)
- [x] Test external imports
- [x] Test exports list
- [x] Test bundled modules table
- [x] Test CSS bundle reference
- [x] Test import kinds (static, dynamic, require)
- [x] Test commonly imported modules
- [x] Test largest files sorting (bloat analysis)
- [x] Test output/input ratio
- [x] Test grep-friendly raw data section
- [x] Test entry point markers
- [x] Test external import markers
- [x] Test node_modules markers
All 17 new tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
## Summary
- Removes the `#!/bin/sh` shebang from placeholder `bin/bun.exe` and
`bin/bunx.exe` scripts in the npm package
- Fixes `npm i -g bun` being completely broken on Windows since v1.3.7
## Problem
PR #26259 added a `#!/bin/sh` shebang to the placeholder scripts to show
a helpful error when postinstall hasn't run. However, npm's `cmd-shim`
reads shebangs to generate `.ps1`/`.cmd` wrappers **before** postinstall
runs, and bakes the interpreter path into them. On Windows, the wrappers
referenced `/bin/sh` which doesn't exist, causing:
```
& "/bin/sh$exe" "$basedir/node_modules/bun/bin/bun.exe" $args
~~~~~~~~~~~~~
The term '/bin/sh.exe' is not recognized...
```
Even after postinstall successfully replaced the placeholder with the
real binary, the stale wrappers still tried to invoke `/bin/sh`.
## Fix
Remove the shebang. Without it, `cmd-shim` generates a direct invocation
wrapper that works after postinstall replaces the placeholder. On Unix,
bash/zsh still execute shebang-less files as shell scripts via ENOEXEC
fallback, so the helpful error message is preserved.
## Test plan
- [x] `bun bd test test/regression/issue/24329.test.ts` passes (2/2
tests)
- Manually verify `npm i -g bun` works on Windows
Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Updates oven-sh/mimalloc bun-dev3 branch to latest upstream
microsoft/mimalloc dev3 (ffa38ab8)
- Merged 12 new commits from upstream
### Key upstream changes included:
- fix re-initialization of threads on macOS
- add lock for sub-pagemap allocations
- fix peak commit stat
- fix use of continue in bitmap find_and_clear (fixes rare case of not
finding space while it exists)
## Test plan
- [ ] CI passes
- [ ] Memory allocation tests pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Refactors `tls-sql.test.ts` to use `describeWithContainer` with a
local Docker container instead of external Neon secrets
- Updates `postgres_tls` service to build from Dockerfile (fixes SSL key
permission issues)
- Fixes pg_hba.conf to allow local socket connections for init scripts
## Test plan
- [x] Verified tests pass locally with `bun bd test
test/js/sql/tls-sql.test.ts` (30 tests pass)
- [ ] CI passes on x64 Linux (arm64 Docker tests are currently disabled)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- `napi_typeof` was returning `napi_object` for `AsyncContextFrame`
values, which are internally callable JSObjects
- Native addons that check callback types (e.g. encore.dev's runtime)
would fail with `expect Function, got: Object` and panic
- Added a `jsDynamicCast<AsyncContextFrame*>` check before the final
`napi_object` fallback to correctly report these values as
`napi_function`
Closes#25933
## Test plan
- [x] Verify encore.dev + supertokens reproduction from the issue no
longer panics
- [ ] Existing napi tests continue to pass
Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Fixed inverted logic in `canReceiveData` function in HTTP/2 stream
state handling
- Added gRPC streaming tests to verify correct behavior
## Problem
The `canReceiveData` function had completely inverted logic that
reported incorrect `remoteClose` status:
| Stream State | Before (Wrong) | After (Correct) |
|--------------|----------------|-----------------|
| OPEN | `false` (can't receive) | `true` (can receive) |
| HALF_CLOSED_LOCAL | `false` (can't receive) | `true` (can receive from
remote) |
| HALF_CLOSED_REMOTE | `true` (can receive) | `false` (remote closed) |
| CLOSED | `true` (can receive) | `false` (stream done) |
Per RFC 7540 Section 5.1:
- In `HALF_CLOSED_LOCAL` state, the local endpoint has sent END_STREAM
but can still **receive** data from the remote peer
- In `HALF_CLOSED_REMOTE` state, the remote endpoint has sent END_STREAM
so no more data will be received
## Test plan
- [x] Added gRPC streaming tests covering unary, server streaming,
client streaming, and bidirectional streaming calls
- [x] Verified HTTP/2 test suite passes (same or fewer failures than
before)
- [x] Verified gRPC test suite improves (7 failures vs 9 failures before
+ 2 errors)
Closes#20875🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fix `$`...`.cwd(".")` causing ENOENT error with path ending in
"undefined"
- The same fix applies to `.cwd("")` and `.cwd("./")`
- Falls back to `process.cwd()` when `defaultCwd` is undefined
Closes#26460
## Test plan
- [x] Added regression test in `test/regression/issue/26460.test.ts`
- [x] Verified test fails with `USE_SYSTEM_BUN=1` (reproduces the bug)
- [x] Verified test passes with `bun bd test` (fix works)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
Fix a bug in `appendOptionsEnv` where bare flags (no `=`) that aren't
the last option get a trailing space appended, causing the argument
parser to not recognize them.
For example, `BUN_OPTIONS="--cpu-prof --cpu-prof-dir=profiles"` would
parse `--cpu-prof` as `"--cpu-prof "` (trailing space), so CPU profiling
was never enabled.
## Root Cause
When `appendOptionsEnv` encounters a `--flag` followed by whitespace, it
advances past the whitespace looking for a possible quoted value (e.g.
`--flag "quoted"`). If no quote is found and there's no `=`, it falls
through without resetting `j`, so the emitted argument includes the
trailing whitespace.
## Fix
Save `end_of_flag = j` after scanning the flag name. Add an `else`
branch that resets `j = end_of_flag` when no value (quote or `=`) is
found after the whitespace. This is a 3-line change.
Also fixes a separate bug in `BunCPUProfiler.zig` where `--cpu-prof-dir`
with an absolute path would hit a debug assertion (`path.append` on an
already-rooted path with an absolute input). Changed to `path.join`
which handles both relative and absolute paths correctly.
## Tests
- `test/cli/env/bun-options.test.ts`: Two new tests verifying
`--cpu-prof --cpu-prof-dir=<abs-path>` produces a `.cpuprofile` file,
for both normal and standalone compiled executables.
## Summary
- Fixes#26422
- Preserve HTTP header case when sending requests (e.g., `Content-Type`
instead of `content-type`)
- HTTP headers are technically case-insensitive per RFC 7230, but many
APIs expect specific casing
## Test plan
- [x] Added tests that verify headers are sent with proper case on the
wire
- [x] Tests use raw TCP sockets to capture actual HTTP wire format
- [x] Tests fail with system Bun (lowercase headers), pass with fixed
build
🤖 Generated with [Claude Code](https://claude.ai/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fixes an issue where calling `req.write(data)` followed by `req.end()`
on an HTTP/2 stream would send **three** DATA frames instead of **two**
- This caused AWS ALB and other strict HTTP/2 servers to reject the
connection with `NGHTTP2_FRAME_SIZE_ERROR` (error code 6)
## Root Cause
The `Http2Stream.end()` method was creating an empty buffer
(`Buffer.alloc(0)`) when called without data:
```javascript
if (!chunk) {
chunk = Buffer.alloc(0);
}
return super.end(chunk, encoding, callback);
```
This empty buffer was then passed to the Duplex stream's `end()`, which
triggered `_write()` with the empty buffer before calling `_final()`.
This resulted in:
1. DATA frame with actual data (from `_write`)
2. Empty DATA frame without END_STREAM (from the extra `_write` with
empty buffer)
3. Empty DATA frame with END_STREAM (from `_final`)
The second empty DATA frame was unnecessary and violated some strict
HTTP/2 implementations.
## Fix
Remove the unnecessary empty buffer creation. The Duplex stream's
`end()` method already handles the no-data case correctly by calling
`_final()` directly without calling `_write()`.
## Test plan
- [x] Manually verified with ConnectRPC client and AWS ALB endpoint
- [x] Added regression test
`test/regression/issue/25589-write-end.test.ts`
- [x] Existing HTTP/2 tests pass
- [x] Existing gRPC tests pass
Fixes#25589🤖 Generated with [Claude Code](https://claude.ai/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
- Adds `Bun.JSON5.parse()` and `Bun.JSON5.stringify()` as built-in APIs
- Adds `.json5` file support in the module resolver and bundler
- Parser uses a scanner/parser split architecture with a labeled switch
pattern (like the YAML parser) — the scanner produces typed tokens, the
parser never touches source bytes directly
- 430+ tests covering the official JSON5 test suite, escape sequences,
numbers, comments, whitespace (including all Unicode whitespace types),
unquoted/reserved-word keys, unicode identifiers, deeply nested
structures, garbage input, error messages, and stringify behavior
<img width="659" height="610" alt="Screenshot 2026-01-25 at 12 19 57 AM"
src="https://github.com/user-attachments/assets/e300125a-f197-4cad-90ed-e867b6232a01"
/>
## Test plan
- [x] `bun bd test test/js/bun/json5/json5.test.ts` — 317 tests
- [x] `bun bd test test/js/bun/json5/json5-test-suite.test.ts` — 113
tests from the official JSON5 test suite
- [x] `bun bd test test/js/bun/resolve/json5/json5.test.js` — .json5
module resolution
closes#3175🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
This PR adds 78 stress tests that exercise JSC's JIT compilation tiers.
The tests are ported from WebKit's `JSTests/stress/` and
`JSTests/wasm/stress/` directories, covering all five JIT tiers:
- **FTL** (41 tests): math intrinsics, string ops, regexp, arguments,
exceptions, try-catch, property access, OSR, tail calls
- **DFG** (14 tests): SSA, type conversion, strength reduction,
arguments, internal functions, try-catch, class constructors
- **Allocation sinking / OSR / LICM** (6 tests): varargs, loop
unrolling, LICM
- **Wasm BBQ** (11 tests): fused-if register alloc, OSR with exceptions,
ipint-bbq OSR, tail calls
- **Wasm OMG** (6 tests): recompile, OSR stack slots, tail call clobber
Each test exercises hot loops that trigger JIT tier-up, verifying
correctness of JIT-compiled code.
## Motivation
The goal is to improve stability on platforms that Bun supports but are
not covered by WebKit's EWS (Early Warning System). By running these JIT
stress tests across all CI platforms, we can catch JIT-related
regressions that would otherwise go unnoticed.
## Licensing
Since the test fixtures are derived from WebKit's `JSTests/`, a
`LICENSE` file is included in the test directory with the BSD 2-Clause
license per WebKit's contribution policy.
## Next Steps
As a follow-up, we are considering running these tests specifically
under CPU emulation (without AVX) on baseline builds, to verify that
JIT-generated code does not emit AVX instructions on platforms that do
not support them.
---------
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
- Updates the docstring in static-initializers.test.ts which incorrectly
said "exactly one static initializer" - the test actually expects 2
initializers for both arm64 and x64 since the mimalloc v3 update
- Added a comment explaining that mimalloc v3 adds a static initializer
on arm64
## Background
The test file was updated in c63415c9c9 (Mimalloc v3 update) to expect 2
static initializers on arm64 (changed from 1 to 2), but the comments
were not updated to reflect this change. This PR updates the
documentation to accurately describe the expected behavior.
## Test plan
- [x] No functional changes - documentation only
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
### What does this PR do?
Move `-DENABLE_REMOTE_INSPECTOR=ON` from the debug-only flags to the
macOS common flags so it applies to all build configurations (debug,
release, lto). This was already the case for Linux and Windows.
Without this, `build:release:local` fails because BunDebugger.cpp and
InspectorLifecycleAgent.cpp unconditionally use JSC inspector APIs that
are only available when REMOTE_INSPECTOR is enabled.
### How did you verify your code works?
Build locally
## Summary
- **PROPERTY_NAME_FROM_UTF8 use-after-free:** The macro used
`StringImpl::createWithoutCopying` for ASCII strings, which left
dangling pointers in JSC's atom string table when the caller freed the
input buffer (e.g. napi-rs `CString`). Fixed by using
`Identifier::fromString` which copies only when inserting into the atom
table, but never retains a reference to the caller's buffer.
- **napi_create_external_buffer data lifetime:** `finalize_cb` was
attached via `addFinalizer` (tied to GC of the `JSUint8Array` view)
instead of the `ArrayBuffer` destructor. Extracting `.buffer` and
letting the Buffer get GC'd would free the backing data while the
`ArrayBuffer` still referenced it. Fixed by attaching the destructor to
the `ArrayBuffer` via `createFromBytes`, using an armed
`NapiExternalBufferDestructor` to safely handle the
`JSUint8Array::create` error path.
Closes#26446Closes#26423
## Test plan
- [x] Added regression test `test_napi_get_named_property_copied_string`
-- strdup/free cycles with GC to reproduce the atom table dangling
pointer
- [x] Added regression test `test_external_buffer_data_lifetime` --
extracts ArrayBuffer, drops Buffer, GCs, verifies data is intact
- [x] Both tests pass with `bun bd test` and match Node.js output via
`checkSameOutput`
- [x] Verified `test_external_buffer_data_lifetime` fails without the
fix (data corrupted) and passes on Node.js
- [x] Verified impit reproducer from #26423 works correctly with the fix
Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
### What does this PR do?
Updates WebKit to
5b6a0ac49b
### How did you verify your code works?
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## What does this PR do?
Fixes CMake "No jobs found" error during the build-bun step in CI by
using `BUILDKITE_BUILD_NUMBER` instead of `BUILDKITE_BUILD_ID` (UUID)
for the Buildkite API URL.
### Problem
When `BUN_LINK_ONLY=ON`, `SetupBuildkite.cmake` fetches build info from
the Buildkite API to download artifacts from earlier build steps
(build-cpp, build-zig).
The `BUILDKITE_BUILD_ID` environment variable contains a UUID (e.g.,
`019bee3e-da45-4e9f-b4d8-4bdb5aeac0ac`). When this UUID is used in the
URL, Buildkite returns a **302 redirect** to the numeric build number
URL (e.g., `/builds/35708`).
CMake's `file(DOWNLOAD)` command **does not follow HTTP redirects**, so
the downloaded file is empty. Parsing the empty JSON yields 0 jobs,
triggering the fatal error:
```
CMake Error at cmake/tools/SetupBuildkite.cmake:67 (message):
No jobs found:
https://buildkite.com/bun/bun/builds/019bee3e-da45-4e9f-b4d8-4bdb5aeac0ac
```
### Solution
Prefer `BUILDKITE_BUILD_NUMBER` (numeric, e.g., `35708`) when available,
which doesn't redirect. This environment variable is automatically set
by Buildkite.
## How did you verify your code works?
- Verified UUID URL returns 302: `curl -sS -w '%{http_code}'
"https://buildkite.com/bun/bun/builds/019bee3e-da45-4e9f-b4d8-4bdb5aeac0ac"`
→ `302`
- Verified numeric URL returns 200 with JSON: `curl -sS -H "Accept:
application/json" "https://buildkite.com/bun/bun/builds/35708"` → valid
JSON with jobs array
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fixed all `update-*.yml` workflows that were creating duplicate PRs
every week
## Problem
The update workflows (libarchive, zstd, cares, etc.) were using `${{
github.run_number }}` in the branch name, e.g.:
```yaml
branch: deps/update-libarchive-${{ github.run_number }}
```
This caused a new unique branch to be created on every workflow run, so
the `peter-evans/create-pull-request` action couldn't detect existing
PRs and would create duplicates.
**Evidence:** There are currently 8+ open duplicate PRs for libarchive
alone:
- #26432 deps: update libarchive to v3.8.5 (deps/update-libarchive-56)
- #26209 deps: update libarchive to v3.8.5 (deps/update-libarchive-55)
- #25955 deps: update libarchive to v3.8.5 (deps/update-libarchive-54)
- etc.
## Solution
Changed all workflows to use static branch names, e.g.:
```yaml
branch: deps/update-libarchive
```
This allows the action to:
1. Detect if an existing branch/PR already exists
2. Update the existing PR with new changes instead of creating a new one
3. Properly use `delete-branch: true` when the PR is merged
## Files Changed
- `.github/workflows/update-cares.yml`
- `.github/workflows/update-hdrhistogram.yml`
- `.github/workflows/update-highway.yml`
- `.github/workflows/update-libarchive.yml`
- `.github/workflows/update-libdeflate.yml`
- `.github/workflows/update-lolhtml.yml`
- `.github/workflows/update-lshpack.yml`
- `.github/workflows/update-root-certs.yml`
- `.github/workflows/update-sqlite3.yml`
- `.github/workflows/update-vendor.yml`
- `.github/workflows/update-zstd.yml`
## Test plan
- [x] Verified the change is syntactically correct
- [ ] Wait for next scheduled run of any workflow to verify it updates
existing PR instead of creating a new one
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fix missing semicolons in minified output when using both default and
named imports from `"bun"` module
- The issue occurred in `printInternalBunImport` when transitioning
between star_name, default_name, and items sections without flushing
pending semicolons
## Test plan
- Added regression tests in `test/regression/issue/26371.test.ts`
covering:
- Default + named imports (`import bun, { embeddedFiles } from "bun"`)
- Namespace + named imports (`import * as bun from "bun"; import {
embeddedFiles } from "bun"`)
- Namespace + default + named imports combination
- Verified test fails with `USE_SYSTEM_BUN=1` (reproduces bug)
- Verified test passes with `bun bd test` (fix works)
Fixes#26371🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fixes#26360
- Detects when `Bun.build` is called from within macro mode during
bundling and throws a clear error instead of hanging indefinitely
## Problem
When `Bun.build` API is called to bundle a file that imports from a
macro which itself uses `Bun.build`, the process would hang indefinitely
due to a deadlock:
1. The bundler uses a singleton thread for processing `Bun.build` calls
2. During parsing, when a macro is encountered, it's evaluated on that
thread
3. If the macro calls `Bun.build`, it tries to enqueue to the same
singleton thread
4. The singleton is blocked waiting for macro completion → deadlock
## Solution
Added a check in `Bun.build` that detects when it's called from macro
mode (`vm.macro_mode`) and throws a clear error with guidance:
```
Bun.build cannot be called from within a macro during bundling.
This would cause a deadlock because the bundler is waiting for the macro to complete,
but the macro's Bun.build call is waiting for the bundler.
To bundle code at compile time in a macro, use Bun.spawnSync to invoke the CLI:
const result = Bun.spawnSync(["bun", "build", entrypoint, "--format=esm"]);
```
## Test plan
- [x] Added regression test in `test/regression/issue/26360.test.ts`
- [x] Verified test hangs/fails with system Bun (the bug exists)
- [x] Verified test passes with the fix applied
- [x] Verified regular `Bun.build` (not in macro context) still works
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
- Fix NO_PROXY environment variable to properly respect port numbers
like Node.js and curl do
- Previously `NO_PROXY=localhost:1234` would bypass proxy for all
requests to localhost regardless of port
- Now entries with ports (e.g., `localhost:8080`) do exact host:port
matching, while entries without ports continue to use suffix matching
## Test plan
- Added tests in `test/js/bun/http/proxy.test.js` covering:
- [x] Bypass proxy when NO_PROXY matches host:port exactly
- [x] Use proxy when NO_PROXY has different port
- [x] Bypass proxy when NO_PROXY has host only (no port) - existing
behavior preserved
- [x] Handle NO_PROXY with multiple entries including port
- Verified existing proxy tests still pass
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
- Fix missing return after handling `.Invalid` stream case in response
body rendering
- Add regression test for Bun.serve() concurrent instances (#26394)
## Details
When a response body contains a locked value with an invalid readable
stream (`stream.ptr == .Invalid`), the code would:
1. Call `this.response_body_readable_stream_ref.deinit()`
2. Fall through without returning
This missing `return` caused the code to fall through to subsequent
logic that could set up invalid callbacks on an already-used body value,
potentially causing undefined behavior.
The fix adds `this.doRenderBlob()` to render an empty response body for
the invalid stream case, then returns properly.
## Test plan
- [x] Added `test/regression/issue/26394.test.ts` with tests for
concurrent Bun.serve instances
- [x] Verified test passes with `bun bd test
test/regression/issue/26394.test.ts`
- [x] Verified test passes with system Bun (`USE_SYSTEM_BUN=1 bun test`)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fix Request.text() failure with "TypeError: undefined is not a
function" after many requests on certain platforms
- Set `#js_ref` in `Request.toJS()` for server-created requests,
matching the existing pattern in `Response.toJS()`
## Root Cause
When Request objects are created by the server (via `Request.init()`),
the `#js_ref` field was never initialized. This caused
`checkBodyStreamRef()` to fail silently when called in `toJS()` because
`#js_ref.tryGet()` returned null.
The bug manifested on macOS after ~4,500 requests when GC conditions
were triggered, causing the weak reference lookup to fail and resulting
in "TypeError: undefined is not a function" when calling `req.text()`.
## Fix
The fix mirrors the existing pattern in `Response.toJS()`:
1. Create the JS value first via `js.toJSUnchecked()`
2. Set `#js_ref` with the JS wrapper reference
3. Then call `checkBodyStreamRef()` which can now properly access the JS
value
## Test plan
- [x] Added regression test that exercises Request.text() with 6000
requests and periodic GC
- [x] Existing request tests pass
- [x] HTTP server tests pass
Fixes#26387🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fixes `bun pm pack` not respecting changes to `package.json` made by
prepack/prepare scripts
- Tracks whether lifecycle scripts (prepublishOnly, prepack, prepare)
ran
- If scripts ran, invalidates the cached package.json and re-reads from
disk before creating the tarball
Closes#24314
## Test plan
- Added regression test `test/regression/issue/24314.test.ts` that
verifies package.json modifications from prepack/prepare scripts are
included in the tarball
- All existing pack tests pass (69 tests)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fixed `controller.desiredSize` throwing `TypeError: null is not an
object` when the stream's internal `controlledReadableStream` has been
set to `null` during cleanup
- Added null check in `readableStreamDefaultControllerGetDesiredSize` to
return `null` when the stream reference is null, matching WHATWG Streams
spec behavior for detached/errored streams
## Root Cause
When piping streams (e.g., `fetch` with `ReadableStream` body), cleanup
code in `assignStreamIntoResumableSink` and `readStreamIntoSink` sets
`controlledReadableStream` to `null` for GC purposes. If the user's
`pull()` function is still running asynchronously when this happens,
accessing `controller.desiredSize` throws instead of returning `null`.
## Test plan
- [x] Added regression test `test/regression/issue/26377.test.ts`
- [x] Verified tests pass with `bun bd test
test/regression/issue/26377.test.ts`
Fixes#26377🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fixed `BUN_OPTIONS` environment variable not being applied as runtime
options for standalone executables (`bun build --compile`). Previously,
args from `BUN_OPTIONS` were incorrectly passed through to
`process.argv` instead of being parsed as Bun runtime options
(`process.execArgv`).
- Removed `BUN_CPU_PROFILE`, `BUN_CPU_PROFILE_DIR`, and
`BUN_CPU_PROFILE_NAME` env vars since `BUN_OPTIONS="--cpu-prof
--cpu-prof-dir=... --cpu-prof-name=..."` now works correctly with
standalone executables.
- Made `cpu_prof.name` and `cpu_prof.dir` non-optional with empty string
defaults.
fixes#21496
## Test plan
- [x] Added tests for `BUN_OPTIONS` with standalone executables (no
`compile-exec-argv`)
- [x] Added tests for `BUN_OPTIONS` combined with `--compile-exec-argv`
- [x] Added tests for `BUN_OPTIONS` with user passthrough args
- [x] Verified existing `compile-argv` tests still pass
- [x] Verified existing `bun-options` tests still pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
## Summary
- Fixes `ws.once()` only working on the first call for each event type
- The bug was in the `#onOrOnce` method which tracked native listeners
via a bitset but didn't account for `once` listeners auto-removing after
firing
- Now only persistent `on()` listeners set the bitset; `once()`
listeners always add new native handlers unless a persistent listener
already exists
## Test plan
- [x] Added regression test `test/regression/issue/26358.test.ts`
- [x] Test verifies `once('message')` works multiple times
- [x] Test verifies `once('pong')` works multiple times
- [x] Test verifies `on()` still works correctly
- [x] Test verifies mixing `on()` and `once()` works correctly
- [x] Verified test fails with `USE_SYSTEM_BUN=1` (bug exists)
- [x] Verified test passes with `bun bd test` (fix works)
Fixes#26358🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
This PR fixes multiple HTTP/2 protocol compliance issues that were
causing stream errors with various HTTP/2 clients (Fauna, gRPC/Connect,
etc.).
fixes https://github.com/oven-sh/bun/issues/12544
fixes https://github.com/oven-sh/bun/issues/25589
### Key Fixes
**Window Size and Settings Handling**
- Fix initial stream window size to use `DEFAULT_WINDOW_SIZE` until
`SETTINGS_ACK` is received
- Per RFC 7540 Section 6.5.1: The sender can only rely on settings being
applied AFTER receiving `SETTINGS_ACK`
- Properly adjust existing stream windows when `INITIAL_WINDOW_SIZE`
setting changes (RFC 7540 Section 6.9.2)
**Header List Size Enforcement**
- Implement `maxHeaderListSize` checking per RFC 7540 Section 6.5.2
- Track cumulative header list size using HPACK entry overhead (32 bytes
per RFC 7541 Section 4.1)
- Reject streams with `ENHANCE_YOUR_CALM` when header list exceeds
configured limit
**Custom Settings Support**
- Add validation for `customSettings` option (up to 10 custom settings,
matching Node.js `MAX_ADDITIONAL_SETTINGS`)
- Validate setting IDs are in range `[0, 0xFFFF]` per RFC 7540
- Validate setting values are in range `[0, 2^32-1]`
**Settings Validation Improvements**
- Use float comparison for settings validation to handle large values
correctly (was using `toInt32()` which truncates)
- Use proper `HTTP2_INVALID_SETTING_VALUE_RangeError` error codes for
Node.js compatibility
**BufferFallbackAllocator** - New allocator that tries a provided buffer
first, falls back to heap:
- Similar to `std.heap.stackFallback` but accepts external buffer slice
- Used with `shared_request_buffer` (16KB threadlocal) for common cases
- Falls back to `bun.default_allocator` for large headers
## Test Plan
- [x] `bun bd` compiles successfully
- [x] Node.js HTTP/2 tests pass: `bun bd
test/js/node/test/parallel/test-http2-connect.js`
- [x] New regression tests for frame size issues: `bun bd test
test/regression/issue/25589.test.ts`
- [x] HTTP/2 continuation tests: `bun bd test
test/js/node/http2/node-http2-continuation.test.ts`
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
### What does this PR do?
Fixes#24007
Possibly fixes https://github.com/oven-sh/bun/issues/18902,
https://github.com/oven-sh/bun/issues/7412
Some filesystems (bind mounts, FUSE, NFS) don't provide `d_type` in
directory entries, returning `DT_UNKNOWN`. This caused glob and
recursive readdir to skip entries entirely.
## Problem
On Linux filesystems that don't populate `d_type` in directory entries
(bind mounts, FUSE, NFS, some ext4 configurations), `readdir()` returns
`DT_UNKNOWN` instead of the actual file type. This caused:
- `Bun.Glob` to skip files/directories entirely
- `fs.readdirSync(..., {recursive: true})` to not recurse into
subdirectories
- `fs.readdirSync(..., {withFileTypes: true})` to report incorrect types
## Solution
Implemented a **lazy `lstatat()` fallback** when `d_type == DT_UNKNOWN`:
- **`sys.zig`**: Added `lstatat()` function - same as `fstatat()` but
with `AT_SYMLINK_NOFOLLOW` flag to correctly identify symlinks
- **`GlobWalker.zig`**: When encountering `.unknown` entries, first
check if filename matches pattern, then call `lstatat()` only if needed
- **`node_fs.zig`**: Handle `.unknown` in both async and sync recursive
readdir paths; propagate resolved kind to Dirent objects
- **`dir_iterator.zig`**: Return `.unknown` for `DT_UNKNOWN` entries,
letting callers handle lazy stat
**Why `lstatat` instead of `fstatat`?** We use `AT_SYMLINK_NOFOLLOW` to
preserve consistent behavior with normal filesystems - symlinks should
be reported as symlinks, not as their target type. This matches [Node.js
behavior](https://github.com/nodejs/node/blob/main/lib/internal/fs/utils.js#L251-L269)
which uses `lstat()` for the DT_UNKNOWN fallback, and follows the lazy
stat pattern established in PR #18172.
### How did you verify your code works?
**Testing:**
- Regression test: `test/regression/issue/24007.test.ts`
- FUSE filesystem test: `test/cli/run/glob-on-fuse.test.ts` (reuses
`fuse-fs.py` from PR #18172, includes symlink verification)
- All existing glob/readdir tests pass
- **Verified in Docker bind-mount environment:**
- Official Bun: `0 files`
- Patched Bun: `3 files`
**Performance:** No impact on normal filesystems - the `.unknown` branch
is only hit when `d_type == DT_UNKNOWN`. The lazy stat pattern avoids
unnecessary syscalls by checking pattern match first.
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### What does this PR do?
Updates WebKit to
87c6cde57d
### How did you verify your code works?
---------
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
- When `bun install` encounters a stale lockfile with a `file:`
dependency path that differs from the package.json, it now shows which
dependency caused the issue instead of the misleading "Bun could not
find a package.json file to install from" error.
## Test plan
- Added regression test in `test/regression/issue/26337.test.ts`
- Verified test fails with system bun (`USE_SYSTEM_BUN=1 bun test
test/regression/issue/26337.test.ts`)
- Verified test passes with debug build (`bun bd test
test/regression/issue/26337.test.ts`)
Fixes#26337🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- When a string exceeds `WTF::String::MaxLength` (~4GB),
`bun.String.createUninitialized()` returns a `.Dead` tag
- The C++ layer now properly throws `ERR_STRING_TOO_LONG` when this
happens
- Updated `String.toJS()` in Zig to return `bun.JSError!jsc.JSValue`
instead of just `jsc.JSValue`
- Updated ~40 Zig caller files to handle the error with `try`
- C++ callers updated with `RETURN_IF_EXCEPTION` checks
## Test plan
- [x] `bun bd test test/js/node/buffer.test.js` - 449 tests pass
- [x] `bun bd
test/js/node/test/parallel/test-buffer-tostring-rangeerror.js` - passes
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Adds environment variable support for enabling CPU profiling without CLI
flags:
- `BUN_CPU_PROFILE=1` — enables the CPU profiler
- `BUN_CPU_PROFILE_DIR=<path>` — sets the output directory for the
profile
- `BUN_CPU_PROFILE_NAME=<name>` — sets the profile file name
These are used as fallbacks when the corresponding `--cpu-prof` CLI
options are not provided. This is useful for profiling in contexts where
modifying the command line isn't practical (e.g. scripts invoked by
other tools).
## Summary
Fixes bytecode alignment in standalone executables to prevent crashes
when loading bytecode cache on Windows.
The bytecode offset needs to be aligned such that when loaded at
runtime, the bytecode pointer is 128-byte aligned. Previously, alignment
was based on arbitrary memory addresses during compilation, which didn't
account for the 8-byte section header prepended at runtime. This caused
the bytecode to be misaligned, leading to segfaults in
`JSC::CachedJSValue::decode` on Windows.
## Root Cause
At runtime, embedded data starts 8 bytes after the PE/Mach-O section
virtual address (which is page-aligned, hence 128-byte aligned). For
bytecode at offset `O` to be aligned:
```
(section_va + 8 + O) % 128 == 0
=> (8 + O) % 128 == 0
=> O % 128 == 120
```
The previous code used `std.mem.alignInSlice()` which found aligned
addresses based on the compilation buffer's arbitrary address, not
accounting for the 8-byte header offset at load time.
## Changes
- **`src/StandaloneModuleGraph.zig`**: Calculate bytecode offset to
satisfy `offset % 128 == 120` instead of using `alignInSlice`
- **`test/regression/issue/26298.test.ts`**: Added regression tests for
bytecode cache in standalone executables
## Test plan
- [x] Added regression test `test/regression/issue/26298.test.ts` with 3
test cases
- [x] Existing `HelloWorldBytecode` test passes
- [x] Build succeeds
Fixes#26298🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
### What does this PR do?
Remove NFKDC normalization and stripVTControlCharacters since
Bun.stringWidth does this now
### How did you verify your code works?
ci
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
- Fixes the `setTimeout.clock` property not being properly deleted after
`jest.useRealTimers()` is called
- Previously, the property was set to `false` instead of deleted,
causing `hasOwnProperty` checks to return `true`
- This broke React Testing Library and other libraries that check for
fake timers using `Object.prototype.hasOwnProperty.call(setTimeout,
'clock')`
## Changes
- Added `JSValue.deleteProperty()` binding in Zig to call JSC's
`deleteProperty()` method
- Updated `setFakeTimerMarker()` in `FakeTimers.zig` to delete the
`clock` property when disabling fake timers
- Updated existing test in `test/regression/issue/25869.test.ts` to
verify correct behavior
- Added new regression test in `test/regression/issue/26284.test.ts`
## Test plan
- [x] Verified new test fails with system bun (before fix)
- [x] Verified new test passes with debug build (after fix)
- [x] Verified existing fake timer tests still pass
- [x] Verified test for issue #25869 passes with fix
Fixes#26284🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fix panic "integer does not fit in destination type" when
reading/writing large files on Windows
- Add chunking for iovec arrays that exceed `c_uint` max entries
- Add chunking for individual buffers that exceed 4GB (`u32` max)
The libuv functions `uv_fs_read` and `uv_fs_write` have two size
limitations:
1. `nbufs` parameter is `c_uint` (32-bit), limiting the number of iovec
entries
2. `uv_buf_t.len` is `ULONG` (u32 on Windows), limiting individual
buffer sizes to 4GB
This change processes large operations in chunks, accumulating results
and updating file positions between chunks.
## Test plan
- [x] Verified `bun run zig:check-all` passes on all platforms
- [x] Verified `bun bd` builds successfully
- [x] Verified basic file read/write operations work correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### What does this PR do?
Fixes#5344Fixes#6356
### How did you verify your code works?
Some test coverage
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
## Summary
Fixes native module export corruption when compiling multiple NAPI
modules with `bun build --compile` on Linux.
- When loading multiple `.node` files in a compiled binary, the second
module would incorrectly get the first module's exports
- Root cause: memfd file descriptors were closed after dlopen, allowing
fd reuse. Since dlopen caches by path (`/proc/self/fd/N`), it returned
the wrong cached handle
- This bug occurs when loading native modules in quick succession, as
the fd number is likely to be reused immediately after being closed
- Fix: Disable the memfd optimization and always use temp files with
unique paths
## Test plan
- [x] Added regression test in `test/regression/issue/26045/`
- [x] Test fails with production bun (v1.3.6)
- [x] Test passes with the fix
Fixes https://github.com/oven-sh/bun/issues/26045🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- Fix typo in `setVerifyMode` where `reject_unauthorized` was
incorrectly reading from `request_cert_js` instead of
`reject_unauthorized_js`
## Test plan
- Existing TLS renegotiation tests pass
- Code inspection shows the fix is correct (simple variable name typo)
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- The 'l' key in `bun update --interactive` now correctly selects the
package when toggling between Target and Latest versions
- Previously, pressing 'l' would toggle `use_latest` but not mark the
package as selected, causing the underline indicator to disappear and
the package not being included when confirming
## Test plan
- [x] Added regression test `test/regression/issue/24131.test.ts` that
verifies 'l' selects the package
- [x] Test fails with system bun (before fix) and passes with debug
build (after fix)
- [x] `bun bd test test/regression/issue/24131.test.ts` passes
Fixes#24131🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
Fixes an index out-of-bounds panic that occurs during bundler code
splitting on Windows.
- The `bytes()` function in `DynamicBitSetUnmanaged` was accessing
`masks[0..numMasks(bit_length) + 1]`, reading one element past the
allocated array
- When the bit set has exactly one mask (bit_length <= 64), this causes
a panic: "index out of bounds: index 1, len 1"
- The bug manifests when sorting chunk deduplication keys derived from
`AutoBitSet.bytes()`
The fix simply removes the erroneous `+ 1` from the slice bounds.
## Test plan
- [x] `bun bd test test/bundler/bundler_splitting.test.ts` - passes
- [x] `bun bd test test/bundler/esbuild/splitting.test.ts` - passes
- [x] `bun bd test test/bundler/bundler_regressions.test.ts` - passes
- [x] `bun bd test test/bundler/bundler_edgecase.test.ts` - passes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fixed `Bun.Terminal` callbacks (data, exit, drain) not being invoked
when the terminal is created inside `AsyncLocalStorage.run()`
## Root Cause
The bug was a redundant `isCallable()` check when storing callbacks in
`initTerminal()`:
1. In `Options.parseFromJS()`, callbacks are validated with
`isCallable()`, then wrapped with `withAsyncContextIfNeeded()`
2. Inside `AsyncLocalStorage.run()`, `withAsyncContextIfNeeded()`
returns an `AsyncContextFrame` object that wraps the callback + async
context
3. An `AsyncContextFrame` is NOT callable - it's a wrapper object. So
the second `isCallable()` check fails
4. Because the check fails, the callback is never stored via
`js.gc.set()`
5. When `onReadChunk()` tries to get the callback, it returns `null` and
the callback is never invoked
## Fix
Removed the redundant `isCallable()` check in `initTerminal()`. The
check was already performed in `parseFromJS()` before wrapping. Other
similar patterns (socket Handlers, Timer) simply store the wrapped
callback without re-checking.
Fixes#26286
## Test plan
- [x] Added regression test in `test/regression/issue/26286.test.ts`
- [x] Verified test fails with system Bun (times out because callback
never invoked)
- [x] Verified test passes with debug build
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fixes TypeScript errors in the react-tailwind template's `build.ts`
when used with the template's strict `tsconfig.json`
## Test plan
- Added regression test `test/regression/issue/24364.test.ts` that
verifies TypeScript compilation passes
- Verified test fails with old template code and passes with fix
Closes#24364🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Replaces empty placeholder executables with shell scripts that print
helpful error messages
- The scripts exit with code 1 instead of silently succeeding with code
0
- Helps users diagnose issues when installing with `--ignore-scripts` or
using pnpm
## Problem
When installing the `bun` npm package with `--ignore-scripts` or using
pnpm (which skips postinstall by default), the placeholder `bun.exe` and
`bunx.exe` files were empty, causing them to silently exit with code 0
and produce no output. This made it very difficult for users to
understand why bun wasn't working.
## Solution
The placeholder files are now shell scripts that:
1. Print a clear error message explaining the issue
2. Provide instructions on how to fix it (manually running postinstall
or reinstalling without `--ignore-scripts`)
3. Exit with code 1 to indicate failure
Example output when running the placeholder:
```
Error: Bun's postinstall script was not run.
This occurs when using --ignore-scripts during installation, or when using a
package manager like pnpm that does not run postinstall scripts by default.
To fix this, run the postinstall script manually:
cd node_modules/bun && node install.js
Or reinstall bun without the --ignore-scripts flag.
```
## Test plan
- [x] Added regression test that verifies the placeholder script
behavior
- [x] Test passes with `bun bd test test/regression/issue/24329.test.ts`
Fixes#24329🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fixed `assert.partialDeepStrictEqual` to correctly handle Map subset
checking
- Previously, Map comparison used `Bun.deepEquals` which required exact
equality
- Now properly checks that all entries in the expected Map exist in the
actual Map with matching values
Fixes#24338
## Test plan
- Added comprehensive test suite in
`test/regression/issue/24338.test.ts` covering:
- Basic subset checking (key2 in Map with key1 and key2)
- Exact match cases
- Empty expected Map
- Multiple matching entries
- Nested objects as values
- Failure cases when expected has more keys
- Failure cases when key is missing in actual
- Failure cases when values differ
- Nested Map values
- Non-string keys
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Extracts credentials from WebSocket URL (`ws://user:pass@host`) and
sends them as Basic Authorization header
- User-provided `Authorization` header takes precedence over URL
credentials
- Credentials are properly URL-decoded before being Base64-encoded
Fixes#24388
## Test plan
- [x] Added regression test `test/regression/issue/24388.test.ts` with 5
test cases:
- Basic credentials in URL
- Empty password
- No credentials (no header sent)
- Custom Authorization header takes precedence
- Special characters (URL-encoded) in credentials
- [x] Tests pass with `bun bd test test/regression/issue/24388.test.ts`
- [x] Tests fail with `USE_SYSTEM_BUN=1 bun test` (confirming the bug
existed)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fix memory leak in socket reconnection path by freeing old resources
before reassignment
- Add regression test for socket connection/close operations
## Problem
When sockets are reused in `connectInner` (common with MongoDB driver
reconnection patterns), the old connection metadata was being
overwritten without being freed first. This caused memory leaks of:
- `connection` (hostname/path strings)
- `protos` (ALPN protocol strings)
- `server_name` (SNI hostname string)
- `socket_context` (SSL context)
## Solution
This fix ensures these resources are properly freed before reassignment
when a socket is reused for reconnection. This matches the cleanup
pattern already used in the socket's `deinit()` function.
## Test plan
- [x] Added regression test `test/regression/issue/24118.test.ts`
- [x] Verified build compiles successfully
- [x] Verified test passes with `bun bd test
test/regression/issue/24118.test.ts`
Fixes#24118🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
- Fix `fs.Dirent.isFIFO()` incorrectly returning `true` for unknown file
types (e.g., on sshfs/NFS mounts)
- Remove the `EventPort` check from `isFIFO()` since `EventPort = 0 =
Unknown`
- Add regression test for the fix
Fixes#24129
## Root Cause
In `NodeDirent.cpp`, the `isFIFO()` method was checking:
```cpp
type == static_cast<int32_t>(DirEntType::NamedPipe) || type == static_cast<int32_t>(DirEntType::EventPort)
```
Since `EventPort = 0` and `Unknown = 0` (they share the same enum
value), any file with unknown type (returned by filesystems like sshfs,
NFS, etc. that don't populate `d_type`) would incorrectly trigger
`isFIFO() === true`.
## Test plan
- [x] Regression test: `bun bd test test/regression/issue/24129.test.ts`
- [x] Existing Dirent tests: `bun bd test test/js/node/fs/fs.test.ts -t
"Dirent"`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fixes a double-free bug in the shell interpreter error handling path
## What Changed
When `interpreter.init()` succeeds but `globalThis.hasException()` is
true, the code was calling `shargs.deinit()` before
`interpreter.finalize()`. However, `interpreter.args` points to `shargs`
after `init()` succeeds, so calling `interpreter.finalize()` ->
`deinitFromFinalizer()` -> `this.args.deinit()` would then try to deinit
an already-freed `ShellArgs`, causing a double-free.
This is a related issue to #24368, which reported crashes during GC
finalization of ShellInterpreter objects. While the main fix for #24368
was added in v1.3.6 (commit 367eeb308e), this fixes an additional
double-free bug in the error handling path.
## Test plan
- [x] Added regression tests in `test/regression/issue/24368.test.ts`
- [x] Tests pass with `bun bd test test/regression/issue/24368.test.ts`
- [x] Basic shell functionality verified
Fixes#24368🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
- Fixed a panic in `bun add` when HTTP requests fail before receiving
response headers
- The panic "Assertion failure: Expected metadata to be set" now becomes
a graceful error message
## Root Cause
In `src/install/PackageManagerTask.zig`, the code assumed `metadata` is
always non-null and panicked when it wasn't. However, `metadata` can be
null when:
- HTTP request fails before receiving response headers
- Network connection is refused/lost
- Timeout occurs before response
- Firewall blocks/corrupts the response
## Fix
Replaced the panic with proper error handling, following the existing
pattern in `runTasks.zig`:
```zig
const metadata = manifest.network.response.metadata orelse {
// Handle the error gracefully instead of panicking
const err = manifest.network.response.fail orelse error.HTTPError;
// ... show user-friendly error message
};
```
## Test plan
- [x] Added regression test `test/regression/issue/26236.test.ts`
- [x] Test verifies Bun shows graceful error instead of panicking
- [x] `bun bd test test/regression/issue/26236.test.ts` passes
Fixes#26236🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Summary
- Make `bun:ffi`'s TinyCC compiler check standard C compiler environment
variables
- Add support for `C_INCLUDE_PATH` (include paths) and `LIBRARY_PATH`
(library paths)
- Fixes compilation on NixOS and other systems that don't use standard
FHS paths
## Test plan
- [x] Added regression test `test/regression/issue/26249.test.ts` that
verifies:
- Single path in `C_INCLUDE_PATH` works
- Multiple colon-separated paths in `C_INCLUDE_PATH` work
- [x] Verified test fails with system bun (without fix)
- [x] Verified test passes with debug build (with fix)
- [x] Verified existing `cc.test.ts` tests still pass
Closes#26249🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
Optimize `Buffer.swap16()` and `Buffer.swap64()` by replacing
byte-by-byte swapping loops with `__builtin_bswap16/64` compiler
intrinsics.
## Problem
`Buffer.swap16` and `Buffer.swap64` were significantly slower than
Node.js due to inefficient byte-level operations:
- **swap16**: Swapped bytes one at a time in a loop
- **swap64**: Used a nested loop with 4 byte swaps per 8-byte element
## Solution
Replace the manual byte swapping with `__builtin_bswap16/64` intrinsics,
which compile to single CPU instructions (`BSWAP` on x86, `REV` on ARM).
Use `memcpy` for loading/storing values to handle potentially unaligned
buffers safely.
## Benchmark Results (64KB buffer, Apple M4 Max)
| Operation | Bun 1.3.6 | Node.js 24 | This PR | Improvement |
|-----------|-----------|------------|---------|-------------|
| swap16 | 1.00 µs | 0.57 µs | 0.56 µs | **1.79x faster** |
| swap32 | 0.55 µs | 0.77 µs | 0.54 µs | (no change, already fast) |
| swap64 | 2.02 µs | 0.58 µs | 0.56 µs | **3.6x faster** |
Bun now matches or exceeds Node.js performance for all swap operations.
## Notes
- `swap32` was not modified as the compiler already optimizes the 4-byte
swap pattern
- All existing tests pass
## Summary
- Remove unreachable dead code that checked for data URLs in
`fetchImpl()`
- Data URLs are already handled earlier in the function via the
`dispatch_request` block which processes `.data` scheme URLs
- This redundant check at lines 375-387 could never be reached
## Test plan
- [ ] Verify existing fetch tests pass with `bun bd test
test/js/web/fetch/`
- [ ] Confirm data URL fetching still works correctly (handled by
earlier code path)
## Changelog
<!-- CHANGELOG:START -->
<!-- No user-facing changes - internal code cleanup only -->
<!-- CHANGELOG:END -->
🤖 Generated with [Claude Code](https://claude.com/claude-code) (100%
12-shotted by claude-opus-4-5)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## What does this PR do?
Updates the oven-sh/tinycc fork to the latest upstream TinyCC,
incorporating 30+ upstream commits while preserving all Bun-specific
patches.
### Upstream changes incorporated
- Build system improvements (c2str.exe handling, cross-compilation)
- macOS 15 compatibility fixes
- libtcc debugging support
- pic/pie support for i386
- arm64 alignment and symbol offset fixes
- RISC-V 64 improvements (pointer difference, assembly, Zicsr extension)
- Relocation updates
- Preprocessor improvements (integer literal overflow handling)
- x86-64 cvts*2si fix
- Various bug fixes
### Bun-specific patches preserved
- Fix crash on macOS x64 (libxcselect.dylib memory handling)
- Implement `-framework FrameworkName` on macOS (for framework header
parsing)
- Add missing #ifdef guards for TCC_IS_NATIVE
- Make `__attribute__(deprecated)` a no-op
- Fix `__has_include` with framework paths
- Support attributes after identifiers in enums
- Fix dlsym behavior on macOS (RTLD_SELF first, then RTLD_DEFAULT)
- Various tccmacho.c improvements
### Related PRs
- TinyCC fork CI is passing:
https://github.com/oven-sh/tinycc/actions/runs/21105489093
## How did you verify your code works?
- [x] TinyCC fork CI passes on all platforms (Linux
x86_64/arm64/armv7/riscv64, macOS x86_64/arm64, Windows i386/x86_64)
- [ ] Bun CI passes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fix assertion failure when using HTTP proxy with redirects and socket
closes during redirect processing
- Add `isClosedOrHasError()` checks before `releaseSocket` and
`closeSocket` in `doRedirect`
Fixes#26220
## Root Cause
In `doRedirect` (`src/http.zig:786-797`), the code called
`releaseSocket` or `closeSocket` without checking if the socket was
already closed. When `onClose` is triggered while `is_redirect_pending`
is true, it calls `doRedirect`, but the socket is already closed at that
point, causing the assertion in `HTTPContext.zig:168` to fail:
```zig
assert(!socket.isClosed()); // FAILS - socket IS closed
```
## Fix
Added `!socket.isClosedOrHasError()` checks before socket operations in
`doRedirect`, matching the pattern already used at line 1790 in the same
file.
## Test plan
- [x] All existing proxy redirect tests pass (`bun bd test
test/js/bun/http/proxy.test.ts`)
- [x] Build completes successfully (`bun bd`)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Fix multipart uploads using form-data + node-fetch@2 +
fs.createReadStream() being truncated
- Convert old-style Node.js streams (that don't implement
`Symbol.asyncIterator`) to Web ReadableStreams before passing to native
fetch
## Test plan
- [x] New tests in `test/regression/issue/26225.test.ts` verify:
- Multipart uploads with form-data and createReadStream work correctly
- Async iterable bodies still work (regression test)
- Large file streams work correctly
- [x] Tests fail with `USE_SYSTEM_BUN=1` and pass with debug build
Fixes#26225🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
### What does this PR do?
The `.direnv` folder is created by [direnv](https://direnv.net/) when
using `use flake` in `.envrc` to automatically load the Nix development
shell. Since the repo already includes a flake.nix, developers on NixOS
commonly use direnv (via nix-direnv) to auto-load the environment. This
folder contains cached environment data and should not be committed.
### What does this PR do?
NixOS enables security hardening flags by default in `mkShell` /
`devShells` e.g. `_FORTIFY_SOURCE=2`. This flag adds runtime buffer
overflow checks but requires compiler optimization (`-O1` or higher) to
work, since it needs to inline functions to insert checks.
Debug builds use `-O0` (no optimization), which causes this compilation
error:
`error: _FORTIFY_SOURCE requires compiling with optimization (-O)
[-Werror,-W#warnings]`
This patch is a standard Nix way to disable this specific flag while
keeping other hardening features intact. It doesn't affect release
builds since it's scoped to `devShells`.
### How did you verify your code works?
`bun bd test` successfully runs test cases.
@@ -90,17 +90,17 @@ Our build scripts will automatically detect and use `ccache` if available. You c
## Install LLVM
Bun requires LLVM 19 (`clang` is part of LLVM). This version requirement is to match WebKit (precompiled), as mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
Bun requires LLVM 21.1.8 (`clang` is part of LLVM). This version is enforced by the build system — mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
{% codetabs group="os" %}
```bash#macOS (Homebrew)
$ brew install llvm@19
$ brew install llvm@21
```
```bash#Ubuntu/Debian
$ # LLVM has an automatic installation script that is compatible with all versions of Ubuntu
# Build bun with the local JSC build — this automatically configures and builds JSC
$ bun run build:local
```
Using `bun run build:local` will build Bun in the `./build/debug-local` directory (instead of `./build/debug`), you'll have to change a couple of places to use this new directory:
`bun run build:local` handles everything: configuring JSC, building JSC, and building Bun. On subsequent runs, JSC will incrementally rebuild if any WebKit sources changed. `ninja -Cbuild/debug-local` also works after the first build, and will build Bun+JSC.
The build output goes to `./build/debug-local` (instead of `./build/debug`), so you'll need to update a couple of places:
- The first line in [`src/js/builtins.d.ts`](/src/js/builtins.d.ts)
- The `CompilationDatabase` line in [`.clangd` config](/.clangd) should be `CompilationDatabase: build/debug-local`
@@ -281,7 +276,7 @@ Note that the WebKit folder, including build artifacts, is 8GB+ in size.
If you are using a JSC debug build and using VScode, make sure to run the `C/C++: Select a Configuration` command to configure intellisense to find the debug headers.
Note that if you change make changes to our [WebKit fork](https://github.com/oven-sh/WebKit), you will also have to change [`SetupWebKit.cmake`](/cmake/tools/SetupWebKit.cmake) to point to the commit hash.
Note that if you make changes to our [WebKit fork](https://github.com/oven-sh/WebKit), you will also have to change [`SetupWebKit.cmake`](/cmake/tools/SetupWebKit.cmake) to point to the commit hash.
## Troubleshooting
@@ -304,7 +299,7 @@ The issue may manifest when initially running `bun setup` as Clang being unable
message(FATAL_ERROR"Your computer is running macOS ${MACOS_VERSION}, which is older than the target macOS SDK ${CMAKE_OSX_DEPLOYMENT_TARGET}. To fix this, either:\n"
" - Upgrade your computer to macOS ${CMAKE_OSX_DEPLOYMENT_TARGET} or newer\n"
" - Download a newer version of the macOS SDK from Apple: https://developer.apple.com/download/all/?q=xcode\n"
" - Set -DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOS_VERSION}\n")
"description":"Configure auto-install behavior. One of \"auto\" (default, auto-installs when no node_modules), \"fallback\" (missing packages only), \"force\" (always).",
"hasValue":true,
"valueType":"val",
"choices":[
"auto"
],
"choices":["auto"],
"required":false,
"multiple":false
},
@@ -3827,12 +3781,7 @@
"description":"Set the default order of DNS lookup results. Valid orders: verbatim (default), ipv4first, ipv6first",
@@ -7,9 +7,9 @@ Bytecode caching is a build-time optimization that dramatically improves applica
## Usage
### Basic usage
### Basic usage (CommonJS)
Enable bytecode caching with the `--bytecode` flag:
Enable bytecode caching with the `--bytecode` flag. Without `--format`, this defaults to CommonJS:
```bash terminal icon="terminal"
bun build ./index.ts --target=bun --bytecode --outdir=./dist
@@ -17,7 +17,7 @@ bun build ./index.ts --target=bun --bytecode --outdir=./dist
This generates two files:
- `dist/index.js` - Your bundled JavaScript
- `dist/index.js` - Your bundled JavaScript (CommonJS)
- `dist/index.jsc` - The bytecode cache file
At runtime, Bun automatically detects and uses the `.jsc` file:
@@ -28,14 +28,24 @@ bun ./dist/index.js # Automatically uses index.jsc
### With standalone executables
When creating executables with `--compile`, bytecode is embedded into the binary:
When creating executables with `--compile`, bytecode is embedded into the binary. Both ESM and CommonJS formats are supported:
```bash terminal icon="terminal"
# ESM (requires --compile)
bun build ./cli.ts --compile --bytecode --format=esm --outfile=mycli
# CommonJS (works with or without --compile)
bun build ./cli.ts --compile --bytecode --outfile=mycli
```
The resulting executable contains both the code and bytecode, giving you maximum performance in a single file.
### ESM bytecode
ESM bytecode requires `--compile` because Bun embeds module metadata (import/export information) in the compiled binary. This metadata allows the JavaScript engine to skip parsing entirely at runtime.
Without `--compile`, ESM bytecode would still require parsing the source to analyze module dependencies—defeating the purpose of bytecode caching.
### Combining with other optimizations
Bytecode works great with minification and source maps:
@@ -90,35 +100,9 @@ Larger applications benefit more because they have more code to parse.
- ❌ **Code that runs once**
- ❌ **Development builds**
- ❌ **Size-constrained environments**
- ❌ **Code with top-level await** (not supported)
## Limitations
### CommonJS only
Bytecode caching currently works with CommonJS output format. Bun's bundler automatically converts most ESM code to CommonJS, but **top-level await** is the exception:
```js
// This prevents bytecode caching
const data = await fetch("https://api.example.com");
export default data;
```
**Why**: Top-level await requires async module evaluation, which can't be represented in CommonJS. The module graph becomes asynchronous, and the CommonJS wrapper function model breaks down.
**Workaround**: Move async initialization into a function:
```js
async function init() {
const data = await fetch("https://api.example.com");
return data;
}
export default init;
```
Now the module exports a function that the consumer can await when needed.
### Version compatibility
Bytecode is **not portable across Bun versions**. The bytecode format is tied to JavaScriptCore's internal representation, which changes between versions.
@@ -236,8 +220,6 @@ It's normal for it it to log a cache miss multiple times since Bun doesn't curre
- Compressing `.jsc` files for network transfer (gzip/brotli)
- Evaluating if the startup performance gain is worth the size increase
**Top-level await**: Not supported. Refactor to use async initialization functions.
## What is bytecode?
When you run JavaScript, the JavaScript engine doesn't execute your source code directly. Instead, it goes through several steps:
Bytecode compilation moves parsing overhead for large input files from runtime to bundle time. Your app starts faster, in exchange for making the `bun build` command a little slower. It doesn't obscure source code.
<Warning>
**Experimental:** Bytecode compilation is an experimental feature. Only `cjs` format is supported (which means no
top-level-await). Let us know if you run into any issues!
</Warning>
<Note>Bytecode compilation supports both `cjs` and `esm` formats when used with `--compile`.</Note>
### What do these flags do?
@@ -365,6 +362,23 @@ The `--bytecode` argument enables bytecode compilation. Every time you run JavaS
The `bytecode: boolean` option can be used to generate bytecode for any JavaScript/TypeScript entrypoints. This can greatly improve startup times for large applications. Only supported for `"cjs"` format, only supports `"target": "bun"` and dependent on a matching version of Bun. This adds a corresponding `.jsc` file for each entrypoint.
The `bytecode: boolean` option can be used to generate bytecode for any JavaScript/TypeScript entrypoints. This can greatly improve startup times for large applications. Requires `"target": "bun"` and is dependent on a matching version of Bun.
- **CommonJS**: Works with or without `compile: true`. Generates a `.jsc` file alongside each entrypoint.
- **ESM**: Requires `compile: true`. Bytecode and module metadata are embedded in the standalone executable.
Without an explicit `format`, bytecode defaults to CommonJS.
@@ -97,6 +97,31 @@ Filters respect your [workspace configuration](/pm/workspaces): If you have a `p
bun run --filter foo myscript
```
### Parallel and sequential mode
Combine `--filter` or `--workspaces` with `--parallel` or `--sequential` to run scripts across workspace packages with Foreman-style prefixed output:
```bash terminal icon="terminal"
# Run "build" in all matching packages concurrently
bun run --parallel --filter '*' build
# Run "build" in all workspace packages sequentially
bun run --sequential --workspaces build
# Run glob-matched scripts across all packages
bun run --parallel --filter '*' "build:*"
# Continue running even if one package's script fails
bun run --parallel --no-exit-on-error --filter '*' test
# Run multiple scripts across all packages
bun run --parallel --filter '*' build lint
```
Each line of output is prefixed with the package and script name (e.g. `pkg-a:build | ...`). Without `--filter`/`--workspaces`, the prefix is just the script name (e.g. `build | ...`). When a package's `package.json` has no `name` field, the relative path from the workspace root is used instead.
Use `--if-present` with `--workspaces` to skip packages that don't have the requested script instead of erroring.
### Dependency Order
Bun will respect package dependency order when running scripts. Say you have a package `foo` that depends on another package `bar` in your workspace, and both packages have a `build` script. When you run `bun --filter '*' build`, you will notice that `foo` will only start running once `bar` is done.
If you intend on building WebKit locally (optional), you should install these packages:
If you intend on building WebKit locally (optional, x64 only), you should install these packages:
```ps1 Scoop
scoop install make cygwin python
```
<Note>Cygwin is not required for ARM64 builds as WebKit is provided as a pre-built binary.</Note>
From here on out, it is **expected you use a PowerShell Terminal with `.\scripts\vs-shell.ps1` sourced**. This script is available in the Bun repository and can be loaded by executing it:
@@ -95,17 +95,17 @@ Our build scripts will automatically detect and use `ccache` if available. You c
## Install LLVM
Bun requires LLVM 19 (`clang` is part of LLVM). This version requirement is to match WebKit (precompiled), as mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
Bun requires LLVM 21.1.8 (`clang` is part of LLVM). This version is enforced by the build system — mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
<CodeGroup>
```bash macOS (Homebrew)
brew install llvm@19
brew install llvm@21
```
```bash Ubuntu/Debian
# LLVM has an automatic installation script that is compatible with all versions of Ubuntu
# Build bun with the local JSC build — this automatically configures and builds JSC
bun run build:local
```
Using `bun run build:local` will build Bun in the `./build/debug-local` directory (instead of `./build/debug`), you'll have to change a couple of places to use this new directory:
`bun run build:local` handles everything: configuring JSC, building JSC, and building Bun. On subsequent runs, JSC will incrementally rebuild if any WebKit sources changed. `ninja -Cbuild/debug-local` also works after the first build, and will build Bun+JSC.
The build output goes to `./build/debug-local` (instead of `./build/debug`), so you'll need to update a couple of places:
- The first line in `src/js/builtins.d.ts`
- The `CompilationDatabase` line in `.clangd` config should be `CompilationDatabase: build/debug-local`
@@ -288,7 +283,7 @@ Note that the WebKit folder, including build artifacts, is 8GB+ in size.
If you are using a JSC debug build and using VScode, make sure to run the `C/C++: Select a Configuration` command to configure intellisense to find the debug headers.
Note that if you change make changes to our [WebKit fork](https://github.com/oven-sh/WebKit), you will also have to change `SetupWebKit.cmake` to point to the commit hash.
Note that if you make changes to our [WebKit fork](https://github.com/oven-sh/WebKit), you will also have to change `SetupWebKit.cmake` to point to the commit hash.
## Troubleshooting
@@ -314,7 +309,7 @@ The issue may manifest when initially running `bun setup` as Clang being unable
@@ -298,6 +298,17 @@ This is useful for catching flaky tests or non-deterministic behavior. Each test
The `--rerun-each` CLI flag will override this setting when specified.
### `test.retry`
Default retry count for all tests. Failed tests will be retried up to this many times. Per-test `{ retry: N }` overrides this value. Default `0` (no retries).
```toml title="bunfig.toml" icon="settings"
[test]
retry = 3
```
The `--retry` CLI flag will override this setting when specified.
### `test.concurrentTestGlob`
Specify a glob pattern to automatically run matching test files with concurrent test execution enabled. Test files matching this pattern will behave as if the `--concurrent` flag was passed, running all tests within those files concurrently.
@@ -5,7 +5,7 @@ description: "File types and loaders supported by Bun's bundler and runtime"
The Bun bundler implements a set of default loaders out of the box. As a rule of thumb, the bundler and the runtime both support the same set of file types out of the box.
Bun uses the file extension to determine which built-in _loader_ should be used to parse the file. Every loader has a name, such as `js`, `tsx`, or `json`. These names are used when building [plugins](/bundler/plugins) that extend Bun with custom loaders.
@@ -197,6 +197,53 @@ export default {
</CodeGroup>
### `json5`
**JSON5 loader**. Default for `.json5`.
JSON5 files can be directly imported. Bun will parse them with its fast native JSON5 parser. JSON5 is a superset of JSON that supports comments, trailing commas, unquoted keys, single-quoted strings, and more.
```ts
import config from "./config.json5";
console.log(config);
// via import attribute:
import data from "./data.txt" with { type: "json5" };
```
During bundling, the parsed JSON5 is inlined into the bundle as a JavaScript object.
```ts
var config = {
name: "my-app",
version: "1.0.0",
// ...other fields
};
```
If a `.json5` file is passed as an entrypoint, it will be converted to a `.js` module that `export default`s the parsed object.
description: Use Bun's built-in support for JSON5 files through both runtime APIs and bundler integration
---
In Bun, JSON5 is a first-class citizen alongside JSON, TOML, and YAML. You can:
- Parse and stringify JSON5 with `Bun.JSON5.parse` and `Bun.JSON5.stringify`
- `import` & `require` JSON5 files as modules at runtime (including hot reloading & watch mode support)
- `import` & `require` JSON5 files in frontend apps via Bun's bundler
---
## Conformance
Bun's JSON5 parser passes 100% of the [official JSON5 test suite](https://github.com/json5/json5-tests). The parser is written in Zig for optimal performance. You can view our [translated test suite](https://github.com/oven-sh/bun/blob/main/test/js/bun/json5/json5-test-suite.test.ts) to see every test case.
---
## Runtime API
### `Bun.JSON5.parse()`
Parse a JSON5 string into a JavaScript value.
```ts
import { JSON5 } from "bun";
const data = JSON5.parse(`{
// JSON5 supports comments
name: 'my-app',
version: '1.0.0',
debug: true,
// trailing commas are allowed
tags: ['web', 'api',],
}`);
console.log(data);
// {
// name: "my-app",
// version: "1.0.0",
// debug: true,
// tags: ["web", "api"]
// }
```
#### Supported JSON5 Features
JSON5 is a superset of JSON based on ECMAScript 5.1 syntax. It supports:
- **Comments**: single-line (`//`) and multi-line (`/* */`)
- **Trailing commas**: in objects and arrays
- **Unquoted keys**: valid ECMAScript 5.1 identifiers can be used as keys
- **Single-quoted strings**: in addition to double-quoted strings
- **Multi-line strings**: using backslash line continuations
- **Hex numbers**: `0xFF`
- **Leading & trailing decimal points**: `.5` and `5.`
- **Infinity and NaN**: positive and negative
- **Explicit plus sign**: `+42`
```ts
const data = JSON5.parse(`{
// Unquoted keys
unquoted: 'keys work',
// Single and double quotes
single: 'single-quoted',
double: "double-quoted",
// Trailing commas
trailing: 'comma',
// Special numbers
hex: 0xDEADbeef,
half: .5,
to: Infinity,
nan: NaN,
// Multi-line strings
multiline: 'line 1 \
line 2',
}`);
```
#### Error Handling
`Bun.JSON5.parse()` throws a `SyntaxError` if the input is invalid JSON5:
```ts
try {
JSON5.parse("{invalid}");
} catch (error) {
console.error("Failed to parse JSON5:", error.message);
description: Parse newline-delimited JSON (JSONL) with Bun's built-in streaming parser
---
Bun has built-in support for parsing [JSONL](https://jsonlines.org/) (newline-delimited JSON), where each line is a separate JSON value. The parser is implemented in C++ using JavaScriptCore's optimized JSON parser and supports streaming use cases.
const buffer = new TextEncoder().encode('{"a":1}\n{"b":2}\n');
const results = Bun.JSONL.parse(buffer);
// [{ a: 1 }, { b: 2 }]
```
When passed a `Uint8Array`, a UTF-8 BOM at the start of the buffer is automatically skipped.
### Error handling
If the input contains invalid JSON, `Bun.JSONL.parse()` throws a `SyntaxError`:
```ts
try {
Bun.JSONL.parse('{"valid":true}\n{invalid}\n');
} catch (error) {
console.error(error); // SyntaxError: Failed to parse JSONL
}
```
---
## `Bun.JSONL.parseChunk()`
For streaming scenarios, `parseChunk` parses as many complete values as possible from the input and reports how far it got. This is useful when receiving data incrementally (e.g., from a network stream) and you need to know where to resume parsing.
The `read` value is always a byte offset into the original buffer, making it easy to use with `TypedArray.subarray()` for zero-copy streaming:
```ts
let buf = new Uint8Array(0);
async function processBinaryStream(stream: ReadableStream<Uint8Array>) {
for await (const chunk of stream) {
// Append chunk to buffer
const newBuf = new Uint8Array(buf.length + chunk.length);
newBuf.set(buf);
newBuf.set(chunk, buf.length);
buf = newBuf;
const result = Bun.JSONL.parseChunk(buf);
for (const value of result.values) {
handleRecord(value);
}
// Keep unconsumed bytes
buf = buf.slice(result.read);
}
}
```
### Error recovery
Unlike `parse()`, `parseChunk()` does not throw on invalid JSON. Instead, it returns the error in the `error` property, along with any values that were successfully parsed before the error:
```ts
const input = '{"a":1}\n{invalid}\n{"b":2}\n';
const result = Bun.JSONL.parseChunk(input);
console.log(result.values); // [{ a: 1 }] — values parsed before the error
console.log(result.error); // SyntaxError
console.log(result.read); // 7 — position up to last successful parse
```
---
## Supported value types
Each line can be any valid JSON value, not just objects:
Parse Markdown and render it using custom JavaScript callbacks. This gives you full control over the output format — you can generate HTML with custom classes, React elements, ANSI terminal output, or any other string format.
```ts
const result = Bun.markdown.render("# Hello **world**", {
1. **`children`** — the accumulated content of the element as a string
2. **`meta`** (optional) — an object with element-specific metadata
Return a string to replace the element's rendering. Return `null` or `undefined` to omit the element from the output entirely. If no callback is registered for an element, its children pass through unchanged.
| `del` | `{ children }` | Strikethrough (`~~text~~`) |
| `br` | `{}` | Hard line break (no children) |
### React 18 and older
By default, elements use `Symbol.for('react.transitional.element')` as the `$$typeof` symbol. For React 18 and older, pass `reactVersion: 18` in the options (third argument):
🟡 Partially implemented. `Profiler` API is supported (`Profiler.enable`, `Profiler.disable`, `Profiler.start`, `Profiler.stop`, `Profiler.setSamplingInterval`). Other inspector APIs are not yet implemented.
Wrap text to a specified column width while preserving ANSI escape codes, hyperlinks, and handling Unicode/emoji width correctly. This is a native, high-performance alternative to the popular [`wrap-ansi`](https://www.npmjs.com/package/wrap-ansi) npm package.
```ts
// Basic wrapping at 20 columns
Bun.wrapAnsi("The quick brown fox jumps over the lazy dog", 20);
// => "The quick brown fox\njumps over the lazy\ndog"
// Preserves ANSI escape codes
Bun.wrapAnsi("\u001b[31mThe quick brown fox jumps over the lazy dog\u001b[0m", 20);
// => "\u001b[31mThe quick brown fox\njumps over the lazy\ndog\u001b[0m"
```
### Options
```ts
Bun.wrapAnsi("Hello World", 5, {
hard: true, // Break words that exceed column width (default: false)
wordWrap: true, // Wrap at word boundaries (default: true)
trim: true, // Trim leading/trailing whitespace per line (default: true)
ambiguousIsNarrow: true, // Treat ambiguous-width characters as narrow (default: true)
Default retry count for all tests. Failed tests will be retried up to this many times. Per-test `{ retry: N }` overrides this value. Default `0` (no retries).
```toml title="bunfig.toml" icon="settings"
[test]
retry = 3
```
The `--retry` CLI flag will override this setting when specified.
#### rerunEach
Re-run each test file multiple times to identify flaky tests:
Use the `--retry` flag to automatically retry failed tests up to a given number of times. If a test fails and then passes on a subsequent attempt, it is reported as passing.
```sh terminal icon="terminal"
bun test --retry 3
```
Per-test `{ retry: N }` overrides the global `--retry` value:
```ts
// Uses the global --retry value
test("uses global retry", () => {
/* ... */
});
// Overrides --retry with its own value
test("custom retry", { retry: 1 }, () => {
/* ... */
});
```
You can also set this in `bunfig.toml`:
```toml title="bunfig.toml" icon="settings"
[test]
retry = 3
```
## Rerun tests
Use the `--rerun-each` flag to run each test multiple times. This is useful for detecting flaky or non-deterministic test failures.
* Each line in the input is expected to be a valid JSON value separated by newlines.
*/
namespaceJSONL{
/**
* The result of `Bun.JSONL.parseChunk`.
*/
interfaceParseChunkResult{
/** The successfully parsed JSON values. */
values: unknown[];
/** How far into the input was consumed. When the input is a string, this is a character offset. When the input is a `TypedArray`, this is a byte offset. Use `input.slice(read)` or `input.subarray(read)` to get the unconsumed remainder. */
read: number;
/** `true` if all input was consumed successfully. `false` if the input ends with an incomplete value or a parse error occurred. */
done: boolean;
/** A `SyntaxError` if a parse error occurred, otherwise `null`. Values parsed before the error are still available in `values`. */
error: SyntaxError|null;
}
/**
* Parse a JSONL (JSON Lines) string into an array of JavaScript values.
*
* If a parse error occurs and no values were successfully parsed, throws
* a `SyntaxError`. If values were parsed before the error, returns the
* successfully parsed values without throwing.
*
* Incomplete trailing values (e.g. from a partial chunk) are silently
* ignored and not included in the result.
*
* When a `TypedArray` is passed, the bytes are parsed directly without
* copying if the content is ASCII.
*
* @param input The JSONL string or typed array to parse
* @returns An array of parsed values
* @throws {SyntaxError} If the input starts with invalid JSON and no values could be parsed
# A script that installs the dependencies needed to build and test Bun.
# This should work on Windows 10 or newer with PowerShell.
@@ -387,7 +387,7 @@ function Install-PdbAddr2line {
functionInstall-Llvm{
Install-Packagellvm`
-Commandclang-cl`
-Version"19.1.7"
-Version"21.1.8"
Add-To-Path"$env:ProgramFiles\LLVM\bin"
}
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.