You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
2920fa Catch errors from dest.write() in Readable.prototype.pipe (#28432)
Closes #28431
Problem
When piping an object-mode Readable into a byte-mode Transform/Writable, dest.write() throws ERR_INVALID_ARG_TYPE
because the chunk is an object instead of a string/Buffer/TypedArray.
This error was uncatchable — it crashed the process instead of being
emitted on the destination stream's error event.
Root Cause
In Readable.prototype.pipe, the ondata handler called dest.write(chunk) without a try/catch:
USE_SYSTEM_BUN=1 bun test test/regression/issue/28431.test.ts → FAIL
(bug exists on main)
bun bd test test/regression/issue/28431.test.ts → PASS (fix works)
Verification (robobun, iteration 9): CI on prior commit ea5b22f6:
Lint JS ✅, Format ✅, alpine-aarch64 ✅, debian-x64-ASAN ✅,
windows-aarch64 ✅, darwin builds ✅. Only failure was
linux-aarch64-build-cpp — unrelated to this pure JS change. Latest
commit 1516e0b9 build #41235 still in progress. Diff: two files, no
TODO/FIXME/HACK, no unrelated changes. Test pipes object-mode Readable
into byte-mode Transform, asserts ERR_INVALID_ARG_TYPE on both error
event and async iterator throw — would crash on main without the fix.
Claude reviews LGTM (confirmed fix matches Node.js v22 upstream).
CodeRabbit pre-merge checks all passed.
2d4c2b Fix assertion crash when hostname/unix coerces to empty string (#28426)
Bun.listen() and Bun.connect() crash with an internal assertion
failure when the hostname (or unix) option is a truthy value whose toString() returns an empty string — for example, an empty array []
or new String("").
Root cause: The bindgen IDLLooseNullable conversion checks the
truthiness of the original JS value (objects are truthy), then calls toString() to get a WTF::String. When toString() produces "",
the result is a non-null WTFStringImpl* (the static empty string
singleton) with length == 0. The Zig code then hits assertf(hostname.length() > 0, "truthy bindgen string should not be empty").
Fix: Replace the assertions with proper validation that returns a TypeError when the coerced string is empty.
Repro:
Bun.listen({hostname: [],port: 0,socket: {data(){},open(){},close(){}}});// Before: panic — "Internal assertion failure: truthy bindgen string should not be empty"// After: TypeError — Expected a non-empty "hostname"
🔍 Verified by robobun: Zig fix at Handlers.zig lines 303 and 315
replaces bun.assertf panics with throwInvalidArguments error returns
— matches the existing pattern in the same function (line 319). Return
type bun.JSError!SocketConfig supports this. On main, these lines have bun.assertf(...) which crashes. Regression test at socket.test.ts
lines 786-803 exercises the exact crash path with [] and new String("") as hostname — both are truthy but toString() returns "".
Would crash on main; does not exist on main. Unix test correctly asserts
the bindgen-layer rejection message. Lint JavaScript passed. Buildkite
build #41227 in progress. No TODO/FIXME/HACK in diff. CodeRabbit
actionable comment addressed. Claude Code Review LGTM.
69e92c Fix panic in fd validation when float is outside i64 range (#28364)
Passing a very large float (e.g. -1.5e308) as a file descriptor to any
API that uses FD.fromJSValidated (such as S3Client.write) caused a
panic:
panic(main thread): integer part of floating point value out of bounds
The @​mod(float, 1) != 0 check correctly identifies non-integers, but
very large finite floats like 1e308 are integers in IEEE 754 (all
doubles above 2^52 are), so they pass that check. The subsequent @​intFromFloat then panics because the value exceeds the i64 range.
The fix validates the float against the valid fd range (0 to maxInt(i32)) before the @​intFromFloat conversion.
Verified: CI lint passed, buildkite build pending (build #40545). Diff
is a minimal 3-line reorder in fd.zig moving the range check before @​intFromFloat. Regression test in s3-fd-validation.test.ts exercises
all four panic-triggering cases (large positive/negative floats,
±Infinity). No TODO/FIXME/HACK markers, no unrelated changes, no reviews
to triage.
8f0fd0 Fix crash in FFI linkSymbols when property values are not objects (#28359)
Summary
Bun.FFI.linkSymbols() crashes when the input object has non-object
property values (numbers, strings, booleans).
Root Cause
generateSymbols iterates over all properties of the input object and
passes each value to generateSymbolForFunction, which calls .get() / .getTruthy() on the value. These methods assert that the target is a
JS object via debugAssert(target.isObject()).
The existing validation only checked isEmptyOrUndefinedOrNull(), which
doesn't catch primitive values like numbers, strings, or booleans. When
such a value reaches generateSymbolForFunction, the assertion fails.
Fix
Add !value.isObject() to the existing validation check in generateSymbols to reject non-object property values early with a
proper TypeError.
Repro
Bun.FFI.linkSymbols({foo: 42});// Before: panic - reached unreachable code // After: TypeError: Expected an object for key "foo"
70a2a7 Fix crash in FFI.viewSource when symbol descriptor values are not objects (#28361)
In generateSymbols, the existing check only rejected
null/undefined/empty values before passing them to generateSymbolForFunction. That function calls value.getTruthy() → value.get(), which asserts that the target is an object. Non-object
values like numbers, strings, or booleans would trigger a debug
assertion failure (panic: reached unreachable code).
Add an isObject() check alongside the existing isEmptyOrUndefinedOrNull() check so non-object symbol descriptor
values are properly rejected with a TypeError instead of crashing.
Verified by robobun: CI Lint passes; Format failure is unrelated
(autofix cherry-pick conflict in docs/runtime/cron.mdx); buildkite still
building. Diff adds !value.isObject() guard in generateSymbols (line
1423) preventing crash on non-object symbol descriptors, plus three arg_types.deinit() calls fixing a pre-existing memory leak in error
paths of print, open, and linkSymbols. Regression test in ffi-viewSource-non-object.test.ts asserts TypeError on
number/string/boolean inputs — these would crash on main (debug
assertion in getOwn on non-objects), so the test is meaningful. No
TODO/FIXME/HACK in added lines. CodeRabbit review found no actionable
issues.
0de7a8 Replace :::caution with in cron docs (#28358)
d62f0f Fix caution admonition in cron docs wrapping too much content (#28357)
Native headless browser automation built into the runtime. Two backends,
one API:
WebKit (macOS default) — the system WKWebView. Zero external
dependencies; everything dlopened at first new Bun.WebView(). Same
link-time dylib count as before (4).
Chrome (cross-platform) — Chrome/Chromium via DevTools Protocol
over --remote-debugging-pipe. Auto-detects the binary in $PATH,
standard install locations, and Playwright's cache; or pass backend.path.
await using view=newBun.WebView({width: 800,height: 600});awaitview.navigate("https://bun.sh");awaitview.click("a[href='/docs']");// waits for actionability, native clickawaitview.scroll(0,400);// native wheel event, isTrusted: trueawaitview.scrollTo("#install");// scrolls every ancestor, waits for visibleconsttitle=awaitview.evaluate("document.title");constpng=awaitview.screenshot();awaitBun.write("page.png",png);
Backends
// macOS default — WKWebView. Nothing to install.newBun.WebView({backend: "webkit"});// Chrome — works on macOS + Linux if Chrome/Chromium/Edge is installed.newBun.WebView({backend: "chrome"});// Override the auto-detected binary, pass extra launch flags:newBun.WebView({backend: {type: "chrome",path: "/opt/chrome/chrome",argv: ["--enable-features=NetworkService"],stderr: "inherit",// Chrome's stderr → yours (crash reports). Default: "ignore".},});
Chrome auto-detection checks in order: BUN_CHROME_PATH env var → $PATH (google-chrome-stable, google-chrome, chromium-browser, chromium, microsoft-edge, chrome) → /Applications + ~/Applications bundles (macOS) or /usr/bin + /snap/bin (Linux) →
Playwright cache (~/Library/Caches/ms-playwright / ~/.cache/ms-playwright). Signed bundles before unsigned Playwright
binaries — enterprise endpoint-protection blocks the latter.
One subprocess per Bun process, both backends. The first new Bun.WebView()'s path/argv/stderr/dataStore.directory win;
subsequent views share the same browser via new tabs (Chrome Target.createTarget, WebKit viewId routing). Bun.WebView.closeAll()
force-kills both subprocesses; also runs automatically at process exit.
API
Method
Both backends
navigate(url)
✅
evaluate(expr)
✅
screenshot()
✅
click(x, y) / click(selector)
✅
type(text)
✅
press(key, {modifiers})
✅
scroll(dx, dy)
✅
scrollTo(selector)
✅
goBack() / goForward() / reload()
✅
resize(w, h)
✅
view.url / view.title / view.loading
✅
view.onNavigated / view.onNavigationFailed
✅
Console capture (console: globalThis.console or custom callback)
✅
|
| Bun.WebView.closeAll() static | ✅ |
Native input — every DOM event has isTrusted: true
All input methods dispatch OS-level events, not JS-synthetic ones. Sites
can't distinguish view.click() from a real mouse click.
WebKit path:
click(x, y) — NSEvent mouseDown:/mouseUp: directly to WKWebView's
NSResponder (bypasses [window sendEvent:] which needs makeKeyAndOrderFront:). Resolves after _doAfterProcessingAllPendingMouseEvents: — WebKit's own "all handlers
ran" barrier.
type(text) — _executeEditCommand("InsertText", text). beforeinput/input fire trusted; keydown does not (editing-command
semantics, same as paste). No IME, no smart quotes.
press(key) — named keys map to editing commands; Escape + modifier
chords fall back to raw keyDown:/keyUp:.
scroll(dx, dy) — CGEventCreateScrollWheelEvent → [webview scrollWheel:], double-_doAfterNextPresentationUpdate: barrier to
serialize against the scrolling-tree commit and the ScrollingThread
roundtrip.
Chrome path:Input.dispatchMouseEvent / dispatchKeyEvent / insertText / dispatchScrollEvent — the same CDP commands
Puppeteer/Playwright use. Chrome marks all DevTools-originated events
trusted.
Selector overloads — actionability auto-waiting
awaitview.click("#submit");// waits, then clicks centerawaitview.click("#obscured",{timeout: 1000});// rejects if never actionableawaitview.scrollTo("#footer",{block: "center"});
The actionability predicate matches Playwright's: attached + has size +
in viewport + bounding box stable for 2 consecutive frames + elementFromPoint(center) returns self-or-descendant. Polls at frame
rate via page-side rAF; selector passed through the framework's
argument-marshalling (no string interpolation — '"); alert(1)//' is a
literal passed to querySelector, which throws).
WebKit: callAsyncJavaScript:arguments:...:completionHandler:
(public, macOS 11+). WebKit awaits the returned promise server-side —
one IPC roundtrip for the whole wait loop. Chrome: Runtime.evaluate with awaitPromise:true — same
one-roundtrip semantics.
// In-memory (default) — cookies/localStorage/IndexedDB gone at process exit.newBun.WebView({dataStore: "ephemeral"});// Persistent — shared across views with the same directory.newBun.WebView({dataStore: {directory: "/tmp/profile"}});
Architecture
WebKit
Separate host subprocess (re-exec of bun with BUN_INTERNAL_WEBVIEW_HOST=3 env var) runs CFRunLoopRun() on thread 0
— WKWebView hard-asserts pthread_main_np(). Parent speaks
length-prefixed C++ structs over a socketpair (usockets parent-side, CFFileDescriptor child-side). Socket EOF = parent death = child exit.
Chrome
One socketpair, child-end dup'd to both fd 3 and fd 4. Chrome's --remote-debugging-pipe reads NUL-delimited CDP JSON from fd 3 and
writes replies to fd 4 — same socket, both directions. usockets' bsd_recv calls recv() which needs a real socket (pipes fail ENOTSOCK — the earlier two-pipe layout broke here silently). No
separate DevTools server, no WebSocket, no port races.
GC
JSWebView holds WriteBarrier<JSPromise> slots (one per op type), no Strong<> anywhere. isReachableFromOpaqueRoots reads std::atomic<uint32_t> activity counter — the barriers themselves
aren't safe to read from the GC thread. Both backends' session maps hold Weak<JSWebView>; a dropped view is reaped once its pending ops
complete.
No -fblocks, no link-time libobjc. Blocks are hand-rolled GlobalBlock structs with BLOCK_IS_GLOBAL (Block_copy/release are
no-ops), isa dlsym'd from libSystem. Every
WebKit/AppKit/CoreGraphics/ObjC symbol goes through dlsym.
Tests
97 tests across both backends, green with BUN_JSC_validateExceptionChecks=1. Chrome tests test.todo when no
Chromium-based browser is found (checks the same detection paths as the
runtime). USE_SYSTEM_BUN=1 fails (no Bun.WebView).
9933f7 Fix CookieMap.toJSON() crash with numeric cookie names (#28314)
CookieMap.toJSON() used putDirect to set properties on the result
object, which asserts that the property name is not an array index.
Cookie names can be numeric strings (e.g. "0", "1", "42"), which
parse as array indices and trigger the assertion in JSObject::putDirectInternal.
Use putDirectMayBeIndex instead, which correctly handles both indexed
and named properties. This is consistent with how other Bun code handles
user-controlled property names (e.g. FormData, FetchHeaders, environment
variables).
Verification: CI building (Buildkite #40296, lint/format passing).
Diff is 2-line fix replacing putDirect → putDirectMayBeIndex in both
loops of CookieMap::toJSON, matching the established pattern used in
ZigGlobalObject.cpp (env vars), JSDOMFormData.cpp, JSFetchHeaders.cpp,
and NodeHTTP.cpp. Regression test in cookie-map.test.ts exercises
numeric cookie names ("0", "1", "42") through toJSON(). No
TODO/FIXME/HACK markers. No unrelated changes.
Co-authored-by: Alistair Smith <hi@alistair.sh>
581d45 Fix null pointer dereference in BunString__toJSDOMURL (#28309)
Root cause:BunString__toJSDOMURL calls DOMURL::create() which
returns an ExceptionOr<Ref<DOMURL>>. When the URL string is invalid
(e.g. "unix://[object Bun]"), this returns an exception. toJSNewlyCreated propagates the exception onto the throw scope and
returns an empty JSValue. However, the code then unconditionally
called jsCast<JSDOMURL*>(jsValue.asCell()) on the null cell, causing a
null pointer dereference.
Fix: Add RETURN_IF_EXCEPTION(throwScope, {}) after toJSNewlyCreated to return early when URL parsing fails, properly
propagating the exception back to JavaScript.
Reproduction: Pass a non-string value (like the Bun object) as the unix option to Bun.serve(), then access server.url. The URL
formatter produces "unix://[object Bun]" which is not parseable by the
WHATWG URL parser.
Verified by robobun: Single-line fix adds RETURN_IF_EXCEPTION guard in BunString.cpp:579, consistent with the same pattern used ~10 other
times in the same file. Code path confirmed: server.url → getURL → toJSDOMURL → BunString__toJSDOMURL. Test in server-url-invalid.test.ts reproduces the exact crash scenario
(invalid unix path → unparseable URL → null cell dereference). No
TODO/FIXME/HACK in diff. CI pending (build #40232) but change is minimal
and mechanical.
Co-authored-by: Alistair Smith <hi@alistair.sh>
9e93bf deflake serve-body-leak: skip on ASAN where RSS-based leak detection is not meaningful (#28301)
The serve-body-leak test sends 10k warmup + 10k test requests per test
case (7 cases = 140k requests) with 512KB payloads. Under ASAN this
causes timeouts and crashes because:
ASAN adds 2-3x memory overhead, making RSS measurements meaningless
for leak detection
ASAN significantly slows execution, causing test timeouts (40s/60s
limits)
The subprocess gets OOM-killed under the added memory pressure
RSS-based memory leak detection is not meaningful under ASAN, which has
its own built-in leak detection. Skip these tests on ASAN builds.
66f7c4 fix(compile): use ELF section for standalone binaries on Linux (#26923)
Summary
Standalone executables on Linux previously read their embedded module
graph from /proc/self/exe at startup, which fails when the binary has
execute-only permissions (chmod 111)
Now uses an ELF section approach (.bun section with BUN_COMPILED
linker symbol), matching the existing macOS (__BUN,__bun) and Windows
(.bun PE section) implementations
At runtime, the kernel maps the data via PT_LOAD during execve —
zero file I/O, no read permission needed
How it works
Build time (src/elf.zig): Appends the module graph to the end of
the ELF file, converts PT_GNU_STACK into a PT_LOAD segment to map
it, and stores the new virtual address at the original BUN_COMPILED
location.
Runtime (StandaloneModuleGraph.zig): BUN_COMPILED.size holds
either 0 (not standalone) or the vaddr of the appended data. Just
dereferences a pointer to get the module graph.
Files changed
File
Change
src/elf.zig
New — ELF manipulation module (229 lines)
src/bun.zig
Added elf import
src/bun.js/bindings/c-bindings.cpp
Added Linux BUN_COMPILED in
.bun section
src/StandaloneModuleGraph.zig
Added ELF struct, Linux
inject/read paths, removed /proc/self/exe reading
test/bundler/bun-build-compile.test.ts
4 new Linux-only tests
Test plan
bun bd test test/bundler/bun-build-compile.test.ts — 10/10 pass
bun bd test test/bundler/bun-build-compile-sourcemap.test.ts —
5/5 pass
bun bd test test/bundler/compile-argv.test.ts — 10/10 pass
Manual: small payload + chmod 111 — works
Manual: large payload (>16KB, forces append path) + chmod 111 —
works
The css.test.ts test was flaking on Windows because the Node.js client
subprocess would sometimes exit before the HMR socket connection message
was printed. This was likely caused by transient fetch failures when the
dev server port wasn't fully ready.
Changes:
Add retry logic to loadPage() in client-fixture.mjs for transient
connection errors
Add unhandledRejection handler to prevent silent client process
crashes
Propagate client process exit code to OutputLineStream for better
error diagnostics
Add null check for content-type header to prevent TypeError
Wrap initial page load in try/catch to handle happy-dom errors
Co-authored-by: Alistair Smith <hi@alistair.sh>
923690 fix(dns): stale cache entries never expire while refcount > 0 (#28271)
isExpired returned false whenever refcount > 0, keeping stale
entries alive indefinitely as long as any connect to that host was in
flight. The guard was there to prevent the caller from deinit()-ing
memory an in-flight request still holds; this moves the check to the
caller so expiry and memory safety are decided separately.
Co-authored-by: robobun <bot@oven.sh>
7960fe fix(bundler): prevent crash when CSS entry points are mixed with JS in --compile (#28251)
Summary
Fixes a crash ("index out of bounds" / segfault) in bun build --compile when CSS files are passed as entry points alongside multiple
JS/TS entry points (e.g., via glob expansion like ./public/**/*)
The root cause was that Handler.next in computeChunks used entry
point IDs as direct array indices into js_chunks.values(), but
CSS-only entry points skip JS chunk creation, making the array smaller
than the number of entry points
Introduces a mapping from entry point IDs to their actual JS chunk
indices, with a sentinel for CSS-only entry points
css/css_parser.zig:6747,6937 — 0xFFD (Tibetan mark) vs 0xFFFD
(replacement char)
webcore/Blob.zig:2862 — new Blob([]).slice(0,0,'text/plain').type
drops contentType
ast/P.zig:3894 — ES module top-level this substitutes null, spec
says undefined
node/node_fs.zig:4873 — hex/base64 size calc has encode/decode
direction reversed
Verification (robobun): Comment-only PR across 22 files — confirmed
zero executable code changes by filtering diff for non-comment lines.
Spot-checked key claims against source: SemverString inline bit
semantics (isInline at :220 confirms bit-clear = inline), NaN→0 coercion
(coerceJSValueDoubleTruncatingTT at :840-841), ESM/CJS label swap
(uses_export_keyword = ESM, uses_exports_ref = CJS), rangeOfIdentifier
is a full 70-line implementation (not a stub),
eat_simple_redirect_operator returns bool not optional. Review feedback
from Claude code review (get() doc + Blob duplicate comment) addressed
in follow-up commit 472b150. CI build #40067 in progress; Lint
JavaScript passed. No TODO/FIXME/HACK in added lines.
6c65e7 fix(sql): negotiate MySQL capabilities to support legacy EOF protocol (#28005)
Summary
Fix Bun.SQL with MySQL adapter returning empty SQLResultArray for
SELECT queries against MySQL-compatible databases (StarRocks, TiDB,
SingleStore, etc.) that do not support CLIENT_DEPRECATE_EOF
Properly negotiate capabilities by intersecting client desires with
server-advertised capabilities per the MySQL protocol spec
Handle legacy EOF packets in both result set and prepared statement
parsing
Root Cause
Bun unconditionally set CLIENT_DEPRECATE_EOF = true in its MySQL
capability flags without checking if the server actually supports it.
When connecting to MySQL-compatible databases like StarRocks that do not
support this capability, the server sends legacy intermediate EOF
packets between column definitions and row data. Bun misinterpreted this
EOF as end-of-result-set, silently returning 0 rows.
Changes
Capability negotiation (Capabilities.zig): Added intersect()
method. During handshake, client capabilities are now ANDed with server
capabilities — only using features both sides support.
Legacy EOF handling (MySQLConnection.zig): When CLIENT_DEPRECATE_EOF is not negotiated:
The intermediate EOF between column definitions and row data is
properly skipped
The final EOF after rows correctly terminates the result set
Legacy EOF packets during prepared statement parsing are also handled
Error set (AnyMySQLError.zig): Added InvalidEOFPacket error
variant.
Test plan
Added regression test (test/regression/issue/28004.test.ts) with
a mock MySQL server that simulates legacy EOF protocol (no CLIENT_DEPRECATE_EOF)
Verified test fails on system bun 1.3.10 (rows.length === 0)
and passes with the fix (rows.length === 2)
Existing MySQL integration tests should continue to pass (MySQL
8.x supports CLIENT_DEPRECATE_EOF, so the modern code path is
unchanged)
Verification (robobun): CI Build #39817 still compiling (Lint JS ✅,
Format pending, Buildkite builds in progress). Diff is clean — no
TODO/FIXME/HACK/debugger markers. Confirmed on main: getDefaultCapabilities unconditionally sets CLIENT_DEPRECATE_EOF = true without intersecting server capabilities, so the mock server test
(which omits CLIENT_DEPRECATE_EOF) would return 0 rows on main. The
fix correctly intersects client/server capabilities and handles legacy
EOF in both handleResultSet (intermediate + final EOF) and handlePreparedStatement. Both EOF paths include header_length < 9
disambiguation per MySQL spec. Two unresolved review threads: (1)
pre-existing 0x00-as-OK bug flagged as pre-existing — not introduced by
this PR; (2) header_length < 9 nit — already addressed in code, thread
just not resolved. CodeRabbit critical concern about prepared statement
EOF timing was addressed: legacy mode defers checkIfPreparedStatementIsDone to the EOF handler while CLIENT_DEPRECATE_EOF mode checks after each definition.
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Alistair Smith <hi@alistair.sh>
a7e9ab docs(sqlite): fix inaccurate WAL accordion text about -shm file (#28213)
Followup to #28212 — fixes an inaccuracy in the WAL mode accordion text
flagged in review.
Problem
The accordion text said writes go to both the -wal and -shm files,
and that both get "integrated" into the main database. Only the -wal
file receives writes; the -shm file is a shared-memory index for read
coordination and is never checkpointed.
Fix
Reword to clarify that writes go only to the WAL file, and that the -shm file is a shared-memory index created for read coordination. This
is now consistent with the accurate description in the WAL sidecar file
cleanup section just below.
Verification: CI green (Build #39929 passed, Lint JavaScript passed,
no failures). Docs-only change to docs/runtime/sqlite.mdx — corrects the
WAL accordion text to accurately state that writes go only to the -wal
file and that -shm is a shared-memory index for read coordination,
consistent with SQLite documentation. No TODO/FIXME markers. No test
needed (prose fix). No reviews to triage.
bun:sqlite documentation did not mention that WAL sidecar file
(.db-wal, .db-shm) cleanup behavior varies by platform after db.close(). Users on macOS were surprised to find these files
persisting.
Root Cause
On macOS, Bun dynamically loads the system-provided SQLite
(LAZY_LOAD_SQLITE=1), which Apple builds with persistent WAL enabled
by default. On Linux and Windows, Bun statically links its own
SQLite build (USE_STATIC_SQLITE=ON), which follows upstream defaults
where sidecar files are removed on close.
Bun's close() implementation calls sqlite3_close/sqlite3_close_v2
without any explicit WAL checkpoint or cleanup — the behavior is
entirely determined by the underlying SQLite library.
Fix
Expanded the WAL mode documentation section to:
Explain that -wal and -shm sidecar files may persist on macOS due
to Apple's system SQLite configuration
Explain that Linux/Windows use statically linked SQLite with upstream
defaults (sidecar files typically removed)
Document the deterministic cleanup pattern using fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0) + PRAGMA wal_checkpoint(TRUNCATE) — verified against the existing
test
Added a cross-reference from the .fileControl() section
Verification
All claims verified against implementation:
cmake/Options.cmake: USE_STATIC_SQLITE defaults to OFF on macOS, ON elsewhere
src/bun.js/bindings/sqlite/JSSQLStatement.cpp: close() calls only sqlite3_close/sqlite3_close_v2 with no WAL cleanup
src/js/bun/sqlite.ts: constants.SQLITE_FCNTL_PERSIST_WAL is
exported as 10
test/js/bun/sqlite/sqlite.test.js: existing test at line 1347
confirms the recommended cleanup pattern works
e91fe3 fix(transpiler): preserve null bytes in tagged template literals (#27554)
Summary
Fixed String.raw corrupting null bytes (U+0000) in tagged template
literals by replacing them with the literal string \uFFFD
The UnsignedCodepointIterator used minInt(u32) = 0 as the error
sentinel for invalid UTF-8 sequences, which collided with the valid null
byte codepoint — changed to maxInt(u32) which is beyond the valid
Unicode range
Added regression tests for null bytes in tagged template literals,
untagged template literals, and embedded null bytes
Root Cause
In src/string/immutable/unicode.zig, NewCodePointIterator.next()
used std.math.minInt(CodePointType) as the error sentinel. For the UnsignedCodepointIterator (u32), minInt(u32) = 0, which collides
with the valid null byte codepoint (U+0000). This caused null bytes to
be misidentified as decode errors and replaced with unicode_replacement (U+FFFD). The printer then emitted the literal
6-character string \uFFFD in raw template literals.
The fix uses maxInt instead — maxInt(u32) = 0xFFFFFFFF, which is
well beyond the valid Unicode range (max 0x10FFFF) and can never
collide with a valid codepoint.
Test plan
Regression test: String.raw preserves null bytes in tagged
template literals
Regression test: null bytes in untagged template literals are
preserved
Regression test: null bytes in String.raw with surrounding
content
256bba deflake: split compile-windows-metadata invalid version test into concurrent cases (#28205)
The invalid version format should error gracefully test was running 5
sequential bun build --compile invocations in a single test case. On
slow Windows CI machines under load, this easily exceeds the 90s test
timeout.
Split the sequential loop into test.each so each invalid version is a
separate test case. Since the parent describe block uses .concurrent, these now run in parallel, each with its own timeout
budget.
The three sourcemap tests (generation, loading, loading with large
files) used a continue outer pattern in their error-handling loops.
When a duplicate/stale error was encountered, continue outer jumped
back to reading the next stderr chunk, but any remaining lines from str.split("\n") that had not yet been processed were discarded because str was set to "" inside the inner loop. This lost data and could
cause the test to hang waiting for output that was already consumed and
thrown away.
Additionally, the bundler processes in the sourcemap loading tests used stdout: "inherit" / stderr: "inherit", which could cause pipe buffer
backpressure blocking the bundler.
Fixes both issues with a shared driveErrorReloadCycle helper that:
Preserves unprocessed lines when encountering duplicate errors (uses lines.pop() for trailing partial lines and re-buffers remaining lines
via lines.slice(i + 1))
Pipes bundler stdout/stderr to ignore to avoid pipe buffer
backpressure
Races bundler.exited against the reload driver for early-exit
detection in the two bundler-based tests
Verification: Format and Lint JavaScript CI checks pass. Buildkite build
#39875 still compiling at time of review. Only test/cli/hot/hot.test.ts changed (test-only deflake, no production
code). Jarred's CHANGES_REQUESTED about the nonce mechanism was
addressed in commit 50f3f4ed (nonce removed). No TODO/FIXME/HACK in
added lines. The driveErrorReloadCycle helper correctly preserves
partial line fragments and remaining unprocessed lines, prevents double
onReload calls via the triggered flag, and uses Buffer.alloc per
test/CLAUDE.md convention.
Co-authored-by: Alistair Smith <hi@alistair.sh>
af24e2 fix(install): print error message when security scanner fails (#28196)
Summary
Fix bun install silently exiting with code 1 when the security
scanner encounters an error
The catch-all error handler in install_with_manager.zig called Global.exit(1) without printing any diagnostic, making failures
impossible to debug (especially in CI)
Add error messages to all silent error paths in both install_with_manager.zig and security_scanner.zig
Test plan
bun bd builds successfully
Verified clean install with security scanner works (partial
install mechanism triggers correctly)
Verification (iteration 2): CI build #39850 pending (Lint JS ✅, Format
pending, Buildkite pending). Previous build #39847 failures were vendor
warnings (libuv, tinycc) unrelated to this PR. Diff correctly
centralizes error printing: removes 5 duplicate Output.errGeneric calls
from security_scanner.zig and adds specific messages for 7 error
variants plus a catch-all else in install_with_manager.zig. Also fixes a
real bug where .error was collapsed into SecurityScannerRetryFailed,
losing the original error. Regression test at
test/regression/issue/28193.test.ts asserts stderr contains "security
scanner" and exit code 1 for both nonexistent and invalid scanner
configs — would fail on main where else => {} prints nothing. No
TODO/FIXME/HACK in added lines. CodeRabbit filename nit (028193 to
28193) addressed. Both alii inline review comments (consolidate prints,
use stderr) addressed; alii approved.
Co-authored-by: Alistair Smith <hi@alistair.sh>
1d50d6 fix(test): use chrome-headless-shell on macOS to fix next-pages puppeteer launch (#28200)
Fixes #11255
On macOS CI, Chrome for Testing (.app bundle) intermittently fails to
launch due to macOS Gatekeeper quarantine blocking the binary:
Browser launch attempt 1/3 failed: Failed to launch the browser process!
Browser launch attempt 2/3 failed: Failed to launch the browser process!
Browser launch attempt 3/3 failed: Failed to launch the browser process!
The fix switches to Puppeteer's headless: "shell" mode on macOS, which
uses the chrome-headless-shell standalone binary instead of the full
Chrome for Testing .app bundle. The standalone binary doesn't trigger
Gatekeeper since it's not inside a .app bundle.
Additional hardening:
Don't pass system browser executablePath on macOS in shell mode (it
would point to full Chrome, not the headless shell)
Add chmod +x for downloaded browser binaries in the cache
Increase retry delay from 1s to 3s
Set reasonable timeouts (30s launch, 60s protocol) instead of 0
Co-authored-by: Alistair Smith <alistair@anthropic.com>
5693fc Disable runtime transpiler cache when debugger is active via env var (#28189)
Reproduction
When using VSCode's debug terminal (which sets BUN_INSPECT environment
variable), breakpoints in files over 50KB land at wrong line numbers.
The debugger shows line 1494 instead of the expected line 1979 (from
issue #28159).
Root Cause
The runtime transpiler cache (RuntimeTranspilerCache) has a 50KB
minimum file size threshold. When the cache is hit, the printer step is
skipped entirely, and the cached transpiled output is used directly.
This cached output does not contain the inline //# sourceMappingURL=data:... comment that the debugger frontend needs to
correctly map breakpoint line numbers.
The --inspect/--inspect-wait/--inspect-brk CLI flags correctly
disabled the transpiler cache in Arguments.zig. However, when the
debugger is configured via the BUN_INSPECT environment variable (used
by VSCode's debug terminal), the cache was not disabled — the configureDebugger() function in VirtualMachine.zig did not set RuntimeTranspilerCache.is_disabled = true.
Fix
Move the cache disable into configureDebugger() so it runs whenever
the inspector is enabled, regardless of how it was activated (CLI flag
or environment variable). This ensures the printer always runs and
generates the inline source map for the debugger frontend.
Verification
USE_SYSTEM_BUN=1 bun test test/regression/issue/28159.test.ts → FAILS (cache file is created with BUN_INSPECT set)
bun bd test test/regression/issue/28159.test.ts → PASSES (cache
is disabled when inspector is enabled)
Closes #28159
Verified by robobun: CI build #39802 still in progress (all cpp builds
passed, zig builds pending); previous build #39801 failures were
unrelated vendored code warnings (libuv/tinycc) and infra issues. Diff
is clean — no TODO/FIXME/HACK. Single Zig line adds RuntimeTranspilerCache.is_disabled = true inside configureDebugger()
(already done in Arguments.zig for CLI flags, this closes the
BUN_INSPECT env var gap). Test in test/regression/issue/28159.test.ts
generates a >50KB TS file, runs with BUN_INSPECT +
BUN_RUNTIME_TRANSPILER_CACHE_PATH, and asserts the cache dir stays empty
— this would fail on main where the env var path did not disable
caching. CodeRabbit's Windows cross-platform feedback was addressed in
commit 1ccb921. No unresolved review threads.
Co-authored-by: Alistair Smith <alistair@anthropic.com>
47826b fix(bundler): propagate star import through barrel namespace re-exports (#28173)
Summary
Fixes barrel optimization dropping exports when a barrel re-exports
namespace imports (import * as X from './mod'; export { X }) with sideEffects: false
The BFS propagation now correctly threads alias_is_star through BarrelExportResolution, treating namespace re-exports as star imports
in the target module
Root Cause
When the barrel optimization BFS encountered a re-exported namespace
import like:
It propagated to ./arrays/typed/index.js with is_star = false and an
empty alias. The target barrel's own re-exports (e.g., export { toDataView } from './misc.js') were then incorrectly deferred because
the BFS tried to find an export named "" instead of loading all
exports.
Fix
Added alias_is_star to BarrelExportResolution and used it during BFS
propagation. When the underlying import is import * as ns, the
propagation now uses is_star = true, ensuring the target module's
exports are all loaded.
Verification
USE_SYSTEM_BUN=1 bun test test/regression/issue/28170.test.ts � FAIL (toDataView is not defined)
bun bd test test/regression/issue/28170.test.ts � PASS
Closes #28170
Closes #28137
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Updated Packages