Skip to content
Open
Show file tree
Hide file tree
Changes from 82 commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
f3ca867
domain: route fs callback throws to uncaughtException, unblocking fou…
cirospaciari Jul 18, 2026
e1cde11
test: give the fs.Dir callback test its own directory
cirospaciari Jul 20, 2026
ddcdedf
test: quarantine worker-terminate ASAN crashes, matching main
cirospaciari Jul 20, 2026
e3ddf79
test: quarantine http2 reset-flood ASAN crash
cirospaciari Jul 20, 2026
7cb2340
Merge branch 'claude/port-node-domain-tests' into claude/domain-fs-ca…
cirospaciari Jul 21, 2026
180265a
ci: allow binary size growth for the node-v26 compat stack [allow size]
cirospaciari Jul 21, 2026
636e949
fs: validate fstat/read callbacks; tighten the callback-throw tests
cirospaciari Jul 22, 2026
cd39d40
test: gate the dns.reverse callback-throw case off Windows
cirospaciari Jul 22, 2026
44846a9
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 22, 2026
c59bf9c
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 22, 2026
e51f042
style: hoist pbkdf2 then-handlers, cite node's callback dispatch, run…
cirospaciari Jul 23, 2026
1d959c2
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 23, 2026
7747e40
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 23, 2026
df7437e
review: drop http2-reset-flood quarantine, link the node MakeCallback…
robobun Jul 23, 2026
95d08e5
test: sync expectations.txt byte-identical with main
cirospaciari Jul 23, 2026
19d86bb
test: explain the settle window in the ignored-symlink-callback check
cirospaciari Jul 23, 2026
75b8465
test: match expectations.txt to the PR base, not main
robobun Jul 23, 2026
bc8b131
ci: keep the binary size allowance on the stack tip [allow size]
robobun Jul 23, 2026
95e0933
dns: throw for falsy hostnames like node
cirospaciari Jul 23, 2026
c7d613c
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 23, 2026
7df1243
test: sync test-c-ares.js and test-dns.js falsy-hostname cases to v26…
robobun Jul 23, 2026
9d0cafa
test: drop the stale falsey-values comment upstream removed
cirospaciari Jul 23, 2026
d1d8351
test: restore the falsey-values comment; it is present upstream
cirospaciari Jul 23, 2026
a952f2f
process: exit on a fatal uncaught exception without another loop turn
cirospaciari Jul 23, 2026
5f70edc
test: use proc.stdout.text() directly in runScript, matching file con…
robobun Jul 23, 2026
1b89dfd
Revert "process: exit on a fatal uncaught exception without another l…
robobun Jul 23, 2026
4b66a47
ci: retrigger [allow size]
robobun Jul 23, 2026
3fb0d77
process: exit on a fatal uncaught exception without another loop turn
cirospaciari Jul 24, 2026
72e6cb9
Revert "process: exit on a fatal uncaught exception without another l…
robobun Jul 24, 2026
cbc4a56
Merge branch 'main' into claude/domain-fs-callback-throw [allow size]
robobun Jul 24, 2026
75b166f
crypto: keep the hoisted pbkdf2 handlers with .bind, per review [allo…
robobun Jul 24, 2026
3c6ba76
process: exit on a fatal uncaught exception without another loop turn
cirospaciari Jul 24, 2026
1d5cc9d
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 24, 2026
22132f9
Merge remote-tracking branch 'origin/claude/domain-fs-callback-throw'…
robobun Jul 24, 2026
61c388d
Merge branch 'claude/port-node-domain-tests' (main-merged) into claud…
robobun Jul 24, 2026
87d36b5
runtime: keep Bun-native handler errors print-and-continue [allow size]
robobun Jul 24, 2026
6deeb74
runtime: keep-alive for the error-handler-throws branch too [allow size]
robobun Jul 24, 2026
9c75d50
Merge branch 'main' of https://github.com/oven-sh/bun into HEAD
cirospaciari Jul 24, 2026
834fc20
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 24, 2026
9c199ea
runtime: invert fatal-exit to opt-in at Bun__reportUnhandledError onl…
robobun Jul 24, 2026
44fd64e
Merge branch 'claude/port-node-domain-tests' (main-synced) into claud…
robobun Jul 24, 2026
12686cc
runtime: drop the explicit on_exit() before process_exit in the fatal…
robobun Jul 24, 2026
cd4394c
repl: suppress the fatal uncaught path so async throws keep the sessi…
robobun Jul 24, 2026
2eafe1f
test: pin the Bun.spawn ipc keep-alive contract
cirospaciari Jul 24, 2026
bda2484
webview: keep the frame-loop error reporter on the keep-alive path [a…
robobun Jul 24, 2026
e91e967
Merge remote 'claude/domain-fs-callback-throw' (Bun.spawn ipc test) […
robobun Jul 24, 2026
04a6793
runtime: a reported error no longer kills event-loop liveness
cirospaciari Jul 24, 2026
c6b8e2a
runtime: three fatal-exit nit fixes [allow size]
robobun Jul 24, 2026
ecd803f
runtime: fatal-exit for --unhandled-rejections=throw/strict; keep-ali…
robobun Jul 24, 2026
db34801
runtime: route Bun.cron and node:net onread throws to the fatal path …
robobun Jul 24, 2026
18c5d90
runtime: default unhandled-rejections mode takes the fatal path too […
robobun Jul 24, 2026
7af13d1
runtime: rejection modes skip the uncaught machinery under watch/hot
cirospaciari Jul 24, 2026
7c8b8ec
events: defer EventTarget listener throws to nextTick, then fatal-exi…
robobun Jul 24, 2026
f396a1a
Merge remote 'claude/domain-fs-callback-throw' (rejection watch/hot g…
robobun Jul 24, 2026
9e4018e
events: clear any exception from queueNextTick in queueUncaughtExcept…
robobun Jul 24, 2026
00ee636
crypto: guard native-callback siblings; runtime: don't double-print M…
robobun Jul 24, 2026
3c94676
crypto: carry the custom-promisify args onto the guarded generateKeyP…
robobun Jul 24, 2026
b82fa1b
crypto: guard sign and verify callbacks like their siblings
cirospaciari Jul 24, 2026
862fa19
crypto: guard diffieHellman and the DEP0115 randomBytes aliases; runt…
robobun Jul 24, 2026
15bd3cd
webcore: route reportException back to the fatal path for its remaini…
robobun Jul 24, 2026
f497fca
crypto: forward name/length on guardLastCallback wrappers; test: drop…
robobun Jul 24, 2026
1639267
inspector: catch the best-effort banner writes
cirospaciari Jul 24, 2026
03d77f9
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 24, 2026
a2af853
ci: rebuild [allow size]
cirospaciari Jul 24, 2026
64c8f45
test: give 20144's SIGKILL backstop headroom over slow-build startup
cirospaciari Jul 24, 2026
59b73db
runtime: reword the Mode::Strict hot fall-through comment [allow size]
robobun Jul 24, 2026
4f0ba67
Merge remote-tracking branch 'origin/claude/port-node-domain-tests' i…
robobun Aug 3, 2026
f0c9bd9
ws: don't arm a second native bridge from super.once(); net: route da…
robobun Aug 3, 2026
49f6904
net: narrow the tls-handshake fatal catch to the secureConnection emi…
robobun Aug 3, 2026
8eb8f03
ws: drop the dead `once` parameter from #onOrOnce (now #armAndOn) [al…
robobun Aug 3, 2026
cecb355
net: route the two remaining handshake catches through reportUncaught…
robobun Aug 3, 2026
58d8ab0
trim comments to <=3 lines, cite spec/node source
robobun Aug 3, 2026
9e8c34c
net: drop the redundant inner secureConnection catch [allow size]
robobun Aug 3, 2026
628892a
Merge remote-tracking branch 'origin/claude/port-node-domain-tests' i…
robobun Aug 4, 2026
bde8355
diagnostics_channel,webstreams_adapters: route subscriber/callback th…
robobun Aug 4, 2026
68784f2
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 4, 2026
93eaa15
Merge branch 'main' into domain-fs-callback-throw
cirospaciari Aug 5, 2026
9568cd7
test: disable leak detection in the callback-throw matrix children [a…
robobun Aug 5, 2026
51e9d74
html-rewriter: pin node-parity timing for the detached-rejection test
cirospaciari Aug 5, 2026
336f90f
Merge branch 'port-node-domain-tests' into domain-fs-callback-throw
cirospaciari Aug 7, 2026
9673052
Merge branch 'port-node-domain-tests' into domain-fs-callback-throw
cirospaciari Aug 7, 2026
ba73787
test: drop the timer variant from the repl keep-alive test [allow size]
robobun Aug 7, 2026
cc70759
webview: guard wsOnMessage's report on clearExceptionExceptTerminatio…
robobun Aug 7, 2026
7ce95bb
runtime: drop the outer hot gates from Mode::Strict/Throw rejection a…
robobun Aug 7, 2026
658d499
napi: route async_work complete and finalizer throws to the fatal pat…
robobun Aug 7, 2026
4b143eb
fs,dgram: route watcher and socket listener throws to the fatal path …
robobun Aug 7, 2026
fdb7ab5
fs: deliver watcher aborts as "abort" so the lenient error emit survi…
robobun Aug 7, 2026
3d04722
Trim comments to node-source/spec references
robobun Aug 7, 2026
71f9463
Merge branch 'claude/port-node-domain-tests' into claude/domain-fs-ca…
robobun Aug 7, 2026
06c479b
napi,child_process: route threadsafe function and ipc message throws …
robobun Aug 8, 2026
9298497
test,fs: skip abort entries in promises.watch filter, parent-owned te…
robobun Aug 8, 2026
01aedac
ws,fs: drop the passthrough once() override, gate the abort error emi…
robobun Aug 8, 2026
6f12209
fs: guard the promises.watch native listener [allow size]
robobun Aug 8, 2026
c54662e
fs: report the abort listener error like the sibling emits [allow size]
robobun Aug 8, 2026
db5279b
dgram: reroute the data handler throw inline [allow size]
robobun Aug 8, 2026
a5914e1
test: let the rewriter transform complete past the handled rejection …
robobun Aug 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions src/js/internal/debugger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,17 +232,27 @@ export default function (
if (debugUrl) {
const { protocol, href, host, pathname } = debugUrl;
if (!protocol.includes("unix")) {
Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n");
Bun.write(Bun.stderr, `Listening:\n ${dim(href)}\n`);
Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n").catch(
kIgnoreWriteError,
);
Bun.write(Bun.stderr, `Listening:\n ${dim(href)}\n`).catch(kIgnoreWriteError);
if (protocol.includes("ws")) {
Bun.write(Bun.stderr, `Inspect in browser:\n ${link(`https://debug.bun.sh/#${host}${pathname}`)}\n`);
Bun.write(Bun.stderr, `Inspect in browser:\n ${link(`https://debug.bun.sh/#${host}${pathname}`)}\n`).catch(
kIgnoreWriteError,
);
}
Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n");
Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n").catch(
kIgnoreWriteError,
);
}
} else {
Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n");
Bun.write(Bun.stderr, `Listening on ${dim(url)}\n`);
Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n");
Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n").catch(
kIgnoreWriteError,
);
Bun.write(Bun.stderr, `Listening on ${dim(url)}\n`).catch(kIgnoreWriteError);
Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n").catch(
kIgnoreWriteError,
);
}
}

Expand Down Expand Up @@ -882,6 +892,11 @@ function reset(): string {
return "";
}

// Bun.write returns a promise; the banner writes are best-effort and a
// rejected stderr write (racing writers on a piped stderr, seen on Windows)
// must not become a fatal unhandled rejection.
function kIgnoreWriteError(): void {}

function notify(options): void {
Bun.connect({
...options,
Expand Down
4 changes: 3 additions & 1 deletion src/js/internal/webstreams_adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ class ReadableFromWeb extends Readable {
try {
callback(error);
} catch (error) {
globalThis.reportError(error);
process.nextTick(() => {
throw error;
});
}
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/js/node/diagnostics_channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ function wrapStoreRun(store, data, next, transform = defaultTransform) {
try {
context = transform(data);
} catch (err) {
process.nextTick(() => reportError(err));
process.nextTick(() => {
throw err;
});
return next();
}

Expand Down Expand Up @@ -144,7 +146,9 @@ class ActiveChannel {
const onMessage = this._subscribers[i];
onMessage(data, this.name);
} catch (err) {
process.nextTick(() => reportError(err));
process.nextTick(() => {
throw err;
});
}
}
}
Expand Down
35 changes: 6 additions & 29 deletions src/js/node/dns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,19 +234,6 @@ function validateLocalAddresses(first, second) {
}
}

function invalidHostname(hostname) {
if (invalidHostname.warned) {
return;
}

invalidHostname.warned = true;
process.emitWarning(
`The provided hostname "${String(hostname)}" is not a valid hostname, and is supported in the dns module solely for compatibility.`,
"DeprecationWarning",
"DEP0118",
);
}

function translateLookupOptions(options) {
if (!options || typeof options !== "object") {
options = { family: options };
Expand Down Expand Up @@ -300,13 +287,9 @@ function lookup(hostname, options, callback) {
validateLookupOptions(options);

if (!hostname) {
invalidHostname(hostname);
if (options.all) {
callback(null, []);
} else {
callback(null, null, 4);
}
return;
// Node v26.3.0 throws synchronously without invoking the callback
// (lib/dns.js lookup); the old warn-and-succeed branch predates that.
throw $ERR_INVALID_ARG_VALUE("hostname", hostname, "must be a non-empty string");
Comment thread
robobun marked this conversation as resolved.
}

const family = isIP(hostname);
Expand Down Expand Up @@ -749,15 +732,9 @@ const promises = {
validateLookupOptions(options);

if (!hostname) {
invalidHostname(hostname);
return Promise.$resolve(
options.all
? []
: {
address: null,
family: 4,
},
);
// Node v26.3.0's promises lookup is an async function, so the same
// ERR_INVALID_ARG_VALUE surfaces as a rejection here.
return Promise.$reject($ERR_INVALID_ARG_VALUE("hostname", hostname, "must be a non-empty string"));
}

const family = isIP(hostname);
Expand Down
14 changes: 7 additions & 7 deletions src/js/node/net.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const {
hasObserver,
startPerf,
stopPerf,
reportUncaughtException,
} = require("internal/shared");
import type { Socket, SocketHandler, SocketListener } from "bun";
import type { Server as NetServer, Socket as NetSocket, ServerOpts } from "node:net";
Expand Down Expand Up @@ -299,9 +300,8 @@ function onClientHandshakeComplete(self, socket, verifyError) {
self._secureEstablished = true;
self[kVerifyError] = verifyError ?? null;
self.alpnProtocol = socket.alpnProtocol;
// Node has no try/catch around these emits; a listener throw reaches
// InternalCallbackScope as uncaughtException. reportError mirrors that
// without changing Bun.connect's handshake-throw-to-error-handler contract.
// Node's TLSWrap completion runs via MakeCallback so a listener throw is an uncaughtException;
// mirror that here instead of falling through to Bun.connect's handshake-to-error-handler contract.
// https://github.com/nodejs/node/blob/v26.3.0/lib/internal/tls/wrap.js#L1107
try {
// https://github.com/nodejs/node/blob/v26.3.0/lib/internal/tls/wrap.js#L1662-L1673
Expand Down Expand Up @@ -343,7 +343,7 @@ function onClientHandshakeComplete(self, socket, verifyError) {
// https://github.com/nodejs/node/blob/v26.3.0/lib/internal/tls/wrap.js#L1810
self.emit("secure", self);
} catch (err) {
reportError(err);
reportUncaughtException(err);
}
}
function onConnectEnd() {
Expand Down Expand Up @@ -986,7 +986,7 @@ const ServerHandlers: SocketHandler<NetSocket> = {
// https://github.com/nodejs/node/blob/v26.3.0/lib/internal/tls/wrap.js#L1810
if (!server) self.emit("secure", self);
} catch (err) {
reportError(err);
reportUncaughtException(err);
}
},
error(socket, error) {
Expand Down Expand Up @@ -1690,7 +1690,7 @@ function Socket(options?) {
// The native data dispatch would otherwise route a throw to the
// socket error handler; hand it to the uncaught-exception path
// synchronously the way node's bare call does.
reportError(e);
reportUncaughtException(e);
}
if (self.destroyed) return;
if (ret === false || self.isPaused()) {
Expand Down Expand Up @@ -1720,7 +1720,7 @@ function Socket(options?) {
} catch (e) {
// Same as above: report then fall through so the next slice is
// delivered, matching node's per-onStreamRead behavior.
reportError(e);
reportUncaughtException(e);
}
if (self.destroyed) return;
if (ret === false || self.isPaused()) {
Expand Down
109 changes: 41 additions & 68 deletions src/js/thirdparty/ws.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,98 +371,71 @@ class BunWebSocket extends EventEmitter {
}
}

#onOrOnce(event, listener, once) {
#armAndOn(event, listener) {
if (event === "redirect") {
emitWarning(event, "ws.WebSocket '" + event + "' event is not implemented in bun");
}
if (event === "upgrade" || event === "unexpected-response") {
this.#ensureHandshakeListener();
return once ? super.once(event, listener) : super.on(event, listener);
return super.on(event, listener);
}
const mask = 1 << eventIds[event];
const hasPersistentListener = mask && (this.#eventId & mask) === mask;
// Add a native listener if:
// 1. For `on()`: no native listener exists yet (will be persistent)
// 2. For `once()`: no persistent `on()` listener exists (otherwise the persistent one forwards events)
// If only `once()` listeners exist, each needs its own native listener since they auto-remove
if (mask && !hasPersistentListener) {
// Only set the eventId bit for persistent `on` listeners, not for `once`
if (!once) {
this.#eventId |= mask;
}
// Add a persistent native bridge if one isn't already forwarding this
// event. once() reaches here via super.once() -> this.on(), so there is
// no once-only bridge shape any more.
if (mask && (this.#eventId & mask) !== mask) {
this.#eventId |= mask;
if (event === "open") {
this.#ws.addEventListener(
"open",
() => {
this.emit("open");
},
once,
);
this.#ws.addEventListener("open", () => {
this.emit("open");
});
} else if (event === "close") {
this.#ws.addEventListener(
"close",
({ code, reason, wasClean }) => {
this.emit("close", code, reason, wasClean);
},
once,
);
this.#ws.addEventListener("close", ({ code, reason, wasClean }) => {
this.emit("close", code, reason, wasClean);
});
} else if (event === "message") {
this.#ws.addEventListener(
"message",
({ data }) => {
const isBinary = typeof data !== "string";
if (isBinary) {
this.emit("message", this.#fragments ? [data] : data, isBinary);
} else {
let encoded = encoder.encode(data);
if (this.#binaryType !== "arraybuffer") {
encoded = Buffer.from(encoded.buffer, encoded.byteOffset, encoded.byteLength);
}
this.emit("message", this.#fragments ? [encoded] : encoded, isBinary);
this.#ws.addEventListener("message", ({ data }) => {
const isBinary = typeof data !== "string";
if (isBinary) {
this.emit("message", this.#fragments ? [data] : data, isBinary);
} else {
let encoded = encoder.encode(data);
if (this.#binaryType !== "arraybuffer") {
encoded = Buffer.from(encoded.buffer, encoded.byteOffset, encoded.byteLength);
}
},
once,
);
this.emit("message", this.#fragments ? [encoded] : encoded, isBinary);
}
});
} else if (event === "error") {
this.#ws.addEventListener(
"error",
err => {
if (this.#unexpectedResponseEmitted) return;
this.emit("error", err);
},
once,
);
this.#ws.addEventListener("error", err => {
if (this.#unexpectedResponseEmitted) return;
this.emit("error", err);
});
} else if (event === "ping") {
this.#ws.addEventListener(
"ping",
({ data }) => {
this.emit("ping", data);
},
once,
);
this.#ws.addEventListener("ping", ({ data }) => {
this.emit("ping", data);
});
} else if (event === "pong") {
this.#ws.addEventListener(
"pong",
({ data }) => {
this.emit("pong", data);
},
once,
);
this.#ws.addEventListener("pong", ({ data }) => {
this.emit("pong", data);
});
}
}
return once ? super.once(event, listener) : super.on(event, listener);
return super.on(event, listener);
}

on(event, listener) {
return this.#onOrOnce(event, listener, undefined);
return this.#armAndOn(event, listener);
}

once(event, listener) {
return this.#onOrOnce(event, listener, onceObject);
// super.once() calls this.on() which arms the native bridge; arming a second bridge here would
// re-emit after the once wrapper removed itself and throw "Unhandled error".
return super.once(event, listener);
}

addListener(event, listener) {
return this.#onOrOnce(event, listener, undefined);
return this.#armAndOn(event, listener);
}

prependListener(event, listener) {
Expand All @@ -484,7 +457,7 @@ class BunWebSocket extends EventEmitter {
if (eventIds[event] === undefined) return;
const mask = 1 << eventIds[event];
if ((this.#eventId & mask) === mask) return;
this.#onOrOnce(event, noopBridgeListener, undefined);
this.#armAndOn(event, noopBridgeListener);
super.off(event, noopBridgeListener);
}

Expand Down
Loading