Skip to content

node: v26 compat fixes — tls/net/dns/crypto, errors/whatwg/promise, worker/util/URLPattern, heap-prof/readline/GCProfiler, cli eval (+48 tests) - #35483

Draft
cirospaciari wants to merge 303 commits into
mainfrom
claude/node-v26-fix-tls
Draft

node: v26 compat fixes — tls/net/dns/crypto, errors/whatwg/promise, worker/util/URLPattern, heap-prof/readline/GCProfiler, cli eval (+48 tests)#35483
cirospaciari wants to merge 303 commits into
mainfrom
claude/node-v26-fix-tls

Conversation

@cirospaciari

@cirospaciari cirospaciari commented Jul 24, 2026

Copy link
Copy Markdown
Member

Combines three same-base compat PRs into one review unit (#35487 and #35491 are closed pointing here). +31 upstream Node v26.3.0 tests vendored byte-verbatim, ~20 runtime fixes. All 31 pass CI-style one-process-per-file; preflight clean (2 justified skips below).

tls / net / dns / crypto / timers (+14)

  • privateKeyEngine/privateKeyIdentifier validation with Node's ordering in validateSecureContextOptions
  • kSetKeepAliveInitialDelay stores seconds (Node/libuv convention); call sites convert to ms at the native handle
  • Socket[kReinitializeHandle] defaults the replacement handle like Node's TLSSocket override
  • domain routing: guardCallback enters the captured domain; randomBytes/randomFill/randomInt bridge native callbacks through a ported Domain._errorHandler
  • dns.Resolver({ maxTimeout }) wired through to ARES_OPT_MAXTIMEOUTMS
  • clearTimeout/clearInterval set _idleTimeout = -1 (Node's unenroll) in the Rust timer
  • server.listen on non-IP hosts resolves via dns.lookup({all:true}), filtering fe80::/10 link-local, guarded by _listeningId
  • setMaxSendFragment returns BoringSSL's verdict (clamp + success) instead of an OpenSSL-style range gate; the existing bun test that codified the divergence is updated
  • 3 additional passing upstream tests vendored (dgram handle, net cluster handle, webcrypto webidl)

Justified skips: test-tls-enable-trace{,-cli}.js skip with 1..0 because BoringSSL does not compile SSL_trace() — identical behavior to a Node built against BoringSSL (HAVE_SSL_TRACE: false).

errors / whatwg / promise (+11)

  • unhandledRejection listeners run with JS caller frames (JS trampoline through process.emit; native-context CallSite arrays were empty and crashed Node's common.mustNotCall)
  • native-context warnings get Node's "<name>: <message>" stack header
  • PromiseRejectionHandledWarning only when no rejectionHandled listener (Node's ordering)
  • [nodejs.util.inspect.custom] function name on URL/URLSearchParams/webstream prototypes
  • vendored harness fix: flag-scan breakcontinue (--expose-gc --expose-internals combinations never installed the interceptor)
  • vendored Node's real internal/errors.js + internal/encoding* with the binding entries they need

worker_threads / util / url / URLPattern (+6)

  • worker control-protocol parity: ERR_WORKER_UNSERIALIZABLE_ERROR, Node-style crash on unknown control types, internalBinding('worker').getEnvMessagePort
  • per-value DataCloneError messages in SerializedScriptValue (Symbol(x)/function foo() {} could not be cloned., MessageChannel→needs-transfer)
  • Node BroadcastChannel semantics on the worker_threads subclass (web global untouched); receiveMessageOnPort(broadcastChannel) implemented; fixed a real hang: bc.ref() after close() pinned the event loop forever
  • util.debuglog port (callback arg, enabled accessor, Node output format)
  • URLPattern brand checks: unchecked downcasts returned garbage on foreign this; now Illegal invocation

test/js/node/test/common/index.js drift is intentional and documented: the flag-scan fix above plus a worker-gc shim.

Each fix's tests fail on the unfixed build (verified per-fix during development). Full disposition logs for the three slices: errors/whatwg PR body history in #35487, worker in #35491, both closed pointing here.

heap-prof / readline / GCProfiler / internal shims (+16, folded from #35486)

  • --heap-prof wrote a heap snapshot graph into .heapprofile; now synthesizes the correct V8 sampling heap profile ({head, samples}) from JSC's SamplingProfiler, incl. worker execArgv support. Bun's own test/cli/heap-prof.test.ts asserted the old wrong shape and is updated in the same change (13/13 on the fix, 5/13 fail on the unfixed base).
  • readline: unbound Promise.$reject, ERR_USE_AFTER_CLOSE guards, promises question() rejects instead of throwing, ctrl+C/D AbortError, constructible promises createInterface
  • v8.GCProfiler implemented on JSC::HeapObserver (was a stub)
  • faithful shims: internal/assert, internal/fs/sync_write_stream, internal/net, internal/event_target (NodeEventTarget)

cli eval / --print (+1, folded from #35488)

  • Node --print semantics: print once at drain or process.exit(), promises rendered verbatim via the node util.inspect port; -p -e / --print --eval=-42 exit-9 parsing; bare crypto resolves to node:crypto in -e/-p (also fixed an infinite-recursion hazard in node:crypto's webcrypto export). Two suite assertions pinning the old output format updated (child_process env quoting, install snapshot).

Cross-branch interaction fixed on the combined branch

require('internal/net'): the vendored verbatim shim (pure helpers like isLoopback) exports freshly-minted look-alike symbols; the harness require hook now merges it key-for-key with Bun's real module-private socket symbols recovered from a probe socket. The superseded plugin-shim interceptor was removed.

cirospaciari and others added 30 commits July 22, 2026 10:48
…l tests)

Add node's internal/util test surface to the exposedInternals map:
normalizeEncoding (node-semantics wrapper over the Rust binding),
assertCrypto, getCIDR, isError, assignFunctionName, kEnumerableProperty,
kEmptyObject (reused from internal/shared), and WeakReference. Add
internalBinding('util').isInsideNodeModules to the test binding via
Error.prepareStackTrace CallSites.

In test/common, the --expose-internals branch of the Flags loop now
`continue`s instead of `break`ing so a later --expose-gc on the same
Flags line still installs the gc shim.

Vendors 7 node v26.3.0 tests verbatim:
test-internal-util-{normalizeencoding,assertCrypto,getCIDR,helpers,
objects,weakreference,isinsidenodemodules}.js
Keeps the strong-pin member from being write-only (lint) with identical
semantics. [allow size]
… fallout it unmasks

process.config.variables spelled the key v8_enable_i8n_support, so every
vendored node test gated on common.hasIntl read undefined and skipped its
Intl half. Fixing the key un-skips the Intl halves of 46 vendored files;
11 of them exposed real gaps, all fixed here:

- buffer: implement transcode() — port of node's lib/buffer.js wrapper and
  src/node_i18n.cc converters (simdutf fast paths, U+FFFD decode / '?'
  encode substitution, node's exact error contract)
- url: rework domainToASCII/domainToUnicode to node's semantics (WHATWG
  host parse on a ws:// base, ERR_MISSING_ARGS, string coercion, punycode
  label validation); expose the standalone UTS #46 toASCII for url.parse
- url.parse: adopt node's current host-section scan (tab/CR/LF stripping,
  last-@ auth split), keep IPv6 literals as written, reject
  forbiddenHostCharsIpv6, throw ERR_INVALID_URL on hostname-spoofing routes
- url.format: node's slashes/file:// rules, auth escaping via node's
  noEscapeAuth table, and the WHATWG URL branch with
  {auth,fragment,search,unicode} options
- URL: reject invalid punycode (xn--) hosts for special schemes like ada
  does (constructor, href/host/hostname setters, parse/canParse); parse an
  empty base string (and fail) instead of ignoring it; the error is now
  node's exact plain "TypeError: Invalid URL" with the ERR_INVALID_URL code
  and node's `input` property
- TextDecoder: node's ERR_ENCODING_INVALID_ENCODED_DATA message format
  ("The encoded data was not valid for encoding <label>")
- util.inspect: node-format URL and URLSearchParams output, including the
  showHidden Symbol(context) URLContext reconstruction
- USVString conversions: V8's "Cannot convert a Symbol value to a string"
- internal/test/binding: add the icu binding (toASCII/toUnicode/
  hasConverter) that node removed in nodejs/node#55156, for
  test-icu-punycode
- vendor test/fixtures/wpt/url/resources (toascii.json, urltestdata.json
  from node v24.3.0) and icu-punycode-toascii.json; restore node's
  original Symbol message in six vendored searchparams tests

All 46 hasIntl-gated vendored files pass on release and debug builds
(the 35 previously green plus the 11 this exposed), along with the url,
encoding, buffer, and util regression suites.
Four no-duplicate-conditional-property-access sites from the Intl fallout
commit (inspect constructor lookup, URL context fields, url format/resolve).

[allow size]
Single conflict in internal/shared.ts: the branch's guardCallback/
reportUncaughtException block and main's new resistStopPropagation were
added at the same location; kept both, exports unioned.

[allow size]
…drain

Async fs completions post into concurrent_tasks straight from the work
pool, with no ScriptExecutionContext gate. A worker terminated mid-flight
(or a process exiting with fs ops pending) could run its final queue drain
before the pool thread's post landed, so the ConcurrentTask shell and its
AsyncFSTask payload sat in the dealloc'd-without-Drop VM's queue forever —
LeakSanitizer aborts the asan lanes on both worker-fshandles termination
tests.

Count posters on the JS thread before the pool hand-off, decrement on the
pool thread right after the post, and have both shutdown paths (worker
shutdown and global exit) spin for zero before draining. The wait is
bounded by syscall latency; nothing can schedule new posters past either
wait point because no user JS runs after it.

The cp and recursive-readdir pool tasks post through the same queue and
are not yet counted; they need the same begin/end pairing as a follow-up.

[allow size]
util: implement getSystemErrorMap, getSystemErrorMessage and
_errnoException on top of a new native uv_e ENTRIES table (the same
table getSystemErrorName reads, so names can never diverge), with
libuv's uv_strerror messages keyed by name.

constants: require('constants') gains the binding-parity keys it was
missing (EXTENSIONLESS_FORMAT_*, obsolete SSL_OP_* zeros, ENGINE_METHOD_*,
RSA padding fallbacks) and is now frozen, matching node's lib/constants.js.

process.binding('uv'): add the missing ENOEXEC entry and drop the
non-node colon from the "Unknown system error" fallback in errname().

internal/test/binding: internalBinding('constants') now returns the real
process.binding('constants') object; internalBinding('uv') exposes the
full UV_* code set plus errname()/getErrorMessage(); new credentials
(safeGetenv) and buffer (kMaxLength/kStringMaxLength) bindings; unknown
bindings now throw with code ERR_INVALID_MODULE like node.

bun:internal-for-testing: expose internal/child_process (the real
ChildProcess class plus a port of node's getValidStdio).

test/common: serve byte-identical node v26.3.0 copies of internal/webidl,
internal/socket_list, internal/fs/utils and internal/crypto/{util,webidl,
hashnames} through a small loader (emulated primordials + node-exact
error formatters), and teach the internal/options shim about
--expose-internals.

The 20 added vendored tests pass 3x against a release build, fail under
a tampered shim, and pass on node v26.3.0.
…ygiene

- deepEquals (checkPrototypes): DataViews compare bytes, ArrayBuffers and
  DataViews fall through to own-property comparison, and non-index property
  names must be own on both sides (a prototype-chain lookup could match an
  inherited key).
- assert: the Map identity fast path now consumes the matched entry so one
  actual entry cannot satisfy two expected entries; DataView/ArrayBuffer
  partial comparison also checks own enumerable properties; URL comparison
  uses a slot-backed brand check instead of instanceof.
- require.resolve: builtin ids resolve before options.paths validation,
  like node.
- ResolveMessage: String(err) routes through the same node_message() as
  err.message; BuildMessage gets Error.prototype in its chain, matching
  ResolveMessage.
- process: DEP0111 stack walk stops once the latch is set; the self-kill
  signal-handler map read is main-thread-gated.
- inspect: URLSearchParams formatting iterates via captured prototype
  methods; debuglog captures Math.floor/Number.
- pathToFileURL: empty extended-UNC servername check uses the prefix length.
- tests: drain stderr pipes (fs runScript, heap-prof), exercise the worker
  DEP0119 latch twice, drop a stray fixture file.
- comments: correct stale rustdoc/SAFETY notes (hot reloader kill-signal
  invariant, compile cache allocator, rustdoc attachment).

[allow size]
Matches the documented short_aliases contract: a mapping can no longer
touch the -/-- sentinels or a positional. No behavior change for the
in-tree tables (all froms are flag-shaped).

[allow size]
Port of node's getPathFromURLWin32/getPathFromURLPosix for explicit
{windows} callers (drive letters, UNC hosts via domainToUnicode, encoded
separator rejection, node's exact error messages); the host-platform path
stays on the native Bun.fileURLToPath fast path.

[allow size]
The is_emitting_watch_kill_signal early return unwound back into the
emitting listener, so code after process.exit() ran and remaining signal
listeners fired. Replace the process there instead (the 'exit' event has
already been dispatched by the caller), restoring the noreturn contract
the BunProcess.cpp comment documents. Regression test proves the repro
(the stray should-not-write.txt this branch once carried was this bug's
artifact).

Also gate the new DataView byte comparison to the node-parity deepEquals
instantiations: Bun.deepEquals/bun:test keep their released
plain-object semantics for DataViews.

[allow size]
Replace the PR-added arrow functions in runtime modules with hoisted named
functions (async_hooks hook wrappers and GC-destroy callback, crypto's
node-style .then delivery via bound helpers, the test-binding stack/env
shims, the stdio reducer), and add node v26.3.0 source citations to the
createHook and formatTime ports.

[allow size]
…, stray binary

- Remove the accidentally-committed garbage-env ELF, compile the fixture
  into a temp dir, and gitignore the root path.
- Flush the compile cache in the self-kill at-exit path, matching node's
  RunAtExit ordering (env.cc AtExit(FlushCompileCache)).
- Wire --watch-kill-signal on Windows: store the signal so the
  platform-agnostic pre-reload emit runs JS handlers; sigaction stays unix.
- Wrap util.isDeepStrictEqual in node's 2-arg forwarder (lib/util.js) so the
  internal skipPrototype argument no longer leaks to the public API.

[allow size]
Node v26 follows Unicode 16 through ada::idna (the ICU path was removed in
nodejs/node#55156); the ICU bundled with WebKit carries Unicode 15.1 data,
so domainToASCII returned '' for inputs node maps successfully: U+180E and
U+206A..U+206F became ignored, U+04C0 and U+2183 gained lowercase mappings,
and five CJK compatibility ideographs got corrected mappings. Pre-apply the
delta in the node:url IDNA entry points; WPT toascii cases 66/74/81/82/83
pin every branch of it. The lane split on CI followed each runner's ICU
data version, not the architecture.

[allow size]
Union resolution in test/cli/watch/watch.test.ts: both sides added a test
at the same location (the branch's watch kill-signal process.exit guard and
main's FileWatcher thread-spawn failure propagation test); kept both.

[allow size]
…in paths bypass

- partialDeepStrictEqual's identity fast path now reserves the matched
  entry's index so the index loop and the fast path cannot both consume
  one actual entry (node throws for the double-match shape); the
  skipPrototype map comparison gets the same reservation for consistency
  (its observable divergence traces to the Bun.deepEquals short-circuit,
  tracked separately).
- require.resolve bypasses paths validation only for real builtins via
  Module.isBuiltin, matching node's normalizeRequirableId gate:
  require.resolve("node:nope", {paths:[0]}) now throws
  ERR_INVALID_ARG_TYPE instead of MODULE_NOT_FOUND.
- The work-pool poster counter now also covers AsyncReaddirRecursiveTask
  and AsyncCpTask (the two remaining enqueue_task_concurrent posters), so
  shutdown's wait_for_concurrent_posters covers every fs completion post.

[allow size]
…f-kill

process.kill(pid-self, sig) flushed the compile cache through
persist_at_exit(), whose process-global DONE latch assumed the signal was
fatal. When it isn't — a worker self-kill with the handler on the main
thread, or a default-ignored signal like SIGWINCH — the process survives
with the latch set, so the real exit's persist became a no-op and modules
loaded after the kill never reached the cache. The self-kill path now uses
a non-latching persist_now(); the exit latch stays where exits happen.

Regression test: self-kill with SIGWINCH, then require a module — both
modules must be cached at exit (pre-fix only the first was).

[allow size]
…in node

The 2-arg forwarder added while addressing review assumed node hides the
internal skipPrototype argument, but node v26.3.0 exposes it:
util.isDeepStrictEqual.length === 3 and isDeepStrictEqual(a, b, true)
skips prototype identity — asserted by the vendored upstream
test-util-isDeepStrictEqual.js, which the forwarder broke (both aarch64
lanes; x64 lanes skipped the file). Restore the direct export of the
3-arg comparator; the comment now cites the verified behavior.

[allow size]
Drop the sleep-and-retouch fallback (pipe output written before the
reader attaches waits in the kernel buffer, so no start can be missed),
reassemble lines across chunk boundaries before matching, bound the test
with the file's standard 10s timeout, and fail loudly if the child's
stdout ends early instead of letting the absent-file expects pass
vacuously.

[allow size]
…rding

The branch's ResolveMessage node_message() now emits node's exact
'Cannot find module' text (with Require stack) for require/require.resolve
failures, so the fixture's old Bun-adapted /Cannot find package/ regexes
went stale. Restore upstream v26.3.0 wording; the only remaining deviation
(dropping upstream's /^Error: / anchor, since the error class stringifies
as ResolveMessage) is commented inline.

[allow size]
The line editor redrew the prompt per keystroke (CR + erase-line +
reprint + cursor move) even when stdin/stdout were pipes. Besides being
noise no pipe reader wants, each redraw is several tiny writes, and unix
socketpair send buffers account per-skb overhead — so a spawn(..., stdio:
'pipe') parent that doesn't read promptly wedged the whole REPL inside a
blocking write(2) after a few hundred keystrokes (test-repl-dynamic-import
timed out on every Linux release lane this way). Non-TTY mode now mirrors
node's terminal:false repl: no echo, no redraw, prompt printed once per
line.

Losing the echo unmasked two directive bugs the old output hid: a lone
'use strict' prologue was consumed into scope state and dropped before
the repl transform ran (evaluating to undefined instead of the string,
and with zero parts the transformed IIFE was silently discarded), and
'use asm' was blanked to an empty statement. Reinject the consumed
directive like the CJS wrapper does, create a part when none exist, and
keep 'use asm' a string statement in repl mode — all three now match
node v26.3.0.

Also gitignore the compiled-bundler test outputs (entry, entry.js.map,
nosourcemap_entry) that land in the repo root when tests run from there.

[allow size]
The JS requireResolve wrapper now only extracts options.paths; the
builtin bypass (node resolves builtin ids before validating paths, so
require.resolve('node:fs', {paths:[0]}) must not throw) and the paths
validation both live in functionImportMeta__resolveSyncPrivate. The
native error contract now matches node v26.3.0 exactly: non-array paths
report the 'options.paths' property, elements throw per-index
ERR_INVALID_ARG_TYPE (paths[0]), and a non-string id names 'request'.
Verified case-by-case against node.

[allow size]
isURL is now a C++ host function doing inherits<JSDOMURL>() — immune to
prototype and Symbol.hasInstance tampering — replacing the try/catch
href-getter probe. The partialDeepStrictEqual typed-array, DataView, and
ArrayBuffer branches call a native ordered-with-gaps containment scan
instead of allocating Uint8Array copies and looping in JS. Element
equality follows Object.is at storage width (all NaNs equal, +0/-0
distinct, per-dtype element alignment including Float16 and BigInt64),
and detached buffers throw node's exact TypeError. Verified identical to
node v26.3.0 across gap/order/NaN/-0/bigint/DataView/ArrayBuffer probes.

[allow size]
robobun and others added 29 commits July 26, 2026 14:15
The NODE_CHANNEL_FD unset_cloexec and caught-signal SIG_DFL reset were inside
on_before_reload_process_linux under an OS(LINUX) || OS(FREEBSD) gate, so
macOS skipped the hook entirely: usockets CLOEXEC'd the IPC fd and execve
closed it, and the reloaded image attached to a dead fd (the "IPC to the
parent survives a --watch reload" test failed on darwin). Renamed to
on_before_reload_process_posix under !OS(WINDOWS); the close_range sweep
stays Linux/FreeBSD-only, and reload_process calls the hook on all unix.
formatWhatwgURL rebuilt the href from .search/.hash, which both return ""
for a null and an empty-string component, so url.format(new URL("http://x/?"),
{unicode: true}) dropped the marker where node keeps it. Derive presence
from the href (the only bare # is the fragment delimiter; the char before
it or end-of-href is ? iff the query is present-and-empty).
CachedBytecode::__bun_jsc_generate_cached_bytecode and
RuntimeTranspilerCache::OutputCode::byte_slice are called from
NodeCompileCache.rs / jsc_hooks.rs (different crate than the pub(crate)
sweep narrowed them to); node_process get_cwd uses bun_sys::getcwd directly
(Syscall alias removed); repl editor_mode bool renamed to input_mode enum.
…g does not trip hasInlineStorage

{...process.binding("uv")} (internal/test/binding.ts) asserts
hasInlineStorage() in JSObject::inlineStorage on a constructEmptyObject(...,
0) object under the merged WebKit. Use the default-capacity overload; the
binding carries ~85 properties anyway so zero inline was a non-optimization.
…ered Error.* in isInsideNodeModules

isInsideNodeModules read Error.captureStackTrace/stackTraceLimit/
prepareStackTrace at call time inside a try/finally with no catch, so a
deleted captureStackTrace or a non-writable stackTraceLimit made url.parse
throw. Capture captureStackTrace at module scope (matching assertion_error.ts
/inspect.js/quic.ts) and swallow any throw from the body and the restore so
callers never observe it.
The hoisted deliverCallbackResult/Error + .bind(cb) pattern allocated one
bound-function object per .bind call (same count as the arrows it replaced),
so the 'no closure allocates per invocation' comment was wrong; and .bind
resolves through user-mutable Function.prototype.bind where arrows do not.
…-uncaught

# Conflicts:
#	src/runtime/dispatch.rs
#	src/runtime/node/node_fs.rs
…SIGPWR on Linux) in the pre-execve disposition reset

ipc.rs: the 276e176 merge moved the cpp bridge out of crate:: scope; the
two advanced-buffer wrappers now resolve through bun_jsc::cpp like their
ipc_serialize/ipc_parse siblings.

c-bindings.cpp: on Linux g_wtfConfig.sigThreadSuspendResume is SIGPWR (30),
not an RT signal, so the s >= SIGRTMIN break did not skip it; resetting
SIGPWR to SIG_DFL while the SamplingProfiler/concurrent GC are still
pthread_kill'ing the JS thread with it terminates the process instead of
reloading. Skip it explicitly under OS(LINUX), mirroring BunProcess.cpp's
process.on guard.
…into claude/node-v26-fix-tls

# Conflicts:
#	src/cares_sys/c_ares.rs
#	src/clap/lib.rs
#	src/clap/streaming.rs
#	src/js/internal/test/binding.ts
#	src/js/internal/util/inspect.js
#	src/js/internal/validators.ts
#	src/js/node/readline.ts
#	src/js/node/tls.ts
#	src/jsc/JSGlobalObject.rs
#	src/jsc/VirtualMachine.rs
#	src/jsc/bindings/BunHeapProfiler.cpp
#	src/jsc/bindings/BunHeapProfiler.h
#	src/jsc/bindings/BunProcess.cpp
#	src/jsc/bindings/ErrorCode.cpp
#	src/jsc/bindings/ErrorCode.ts
#	src/jsc/bindings/JSDOMExceptionHandling.h
#	src/jsc/bindings/webcore/SerializedScriptValue.cpp
#	src/options_types/context.rs
#	src/runtime/cli/Arguments.rs
#	src/runtime/cli/mod.rs
#	src/runtime/cli/run_command.rs
#	src/runtime/node/node_process.rs
#	src/runtime/node/path.rs
#	test/js/node/test/common/index.mjs
… the try so a throwing getter is swallowed too
…soned CallSite.prototype.getFileName degrades to false instead of escaping to url.parse
…into claude/node-v26-fix-tls

# Conflicts:
#	src/jsc/bindings/BunProcess.cpp
…llout

- add src/jsc/bindings/BunHeapProfiler.h: GeneratedJS2Native.h #includes it
  for the $newCppFunction("BunHeapProfiler.cpp", ...) calls in v8.ts
- web_worker.rs: parent_ref binding was removed by #36571; deref (*parent)
  like the neighbouring fields
- permission.rs: switch std::sync::RwLock -> bun_threading::RwLock and
  std::env::var -> bun_core::env_var::NODE_OPTIONS (disallowed_types/methods);
  contains() instead of iter().any()
- path.rs: re-expose resolve_{posix,windows}_t as pub(crate) for permission.rs
- clap lib.rs: Diagnostic fields pub (read by bun_runtime Arguments.rs)
- run_command.rs: exec_check pub -> pub(crate) (unreachable_pub)
- BunHeapProfiler.rs: then(|| ..) -> then_some (unnecessary_lazy_evaluations)
- Timer.rs: move SAFETY comment onto the unsafe block it documents
…syncHook

emitImmediateAsyncHook does a raw JSC::call with no local scope. Two
back-to-back calls without a check between them trip the JSC
exception-scope verifier (executeCallImpl -> executeCallImpl) under
validateExceptionChecks. Surfaces in bake/dev tests on x64-asan since
the dev server runs inside AsyncLocalStorage.run and react-dom schedules
via setImmediate.
…l.iterator cannot hide Buffers from the envelope
…descriptor value check so a polluted Object.prototype.value does not make accessor descriptors walk onto the stack
…p describe, JSEvent inspect exception check, fs options.recursive name, oxlint + dead-symbols

- Arguments.rs: drop the pre-permission-model '--permission is not supported'
  rejection block; the implementation landed earlier in the file and the
  rejection overrode it (unblocks ~20 vendored permission tests).
- util.test.js: close the first util.debuglog describe so the two merged
  debuglog test blocks do not overlap (was a parse error).
- JSEvent.cpp: RETURN_IF_EXCEPTION after utilInspectFunction() in the Event
  inspect.custom, matching the other three call sites.
- node_fs.rs: name options-bag boolean members 'options.<key>' so
  ERR_INVALID_ARG_TYPE says 'property', matching Node.
- crypto.ts: drop the placeholder arity params (length is overridden via
  defineProperty); domain.ts: read stack.length once.
- validators.ts: remove the never-referenced validateInternalField export.
- transpiler.test.js: prefix the deeply-nested-unary '-e' payload with a space
  now that Bun matches Node's 'requires an argument' for a dash-leading value.
- node-dns.test.js: compare shape against localhost instead of racing two
  google.com lookups (google.com now has multiple A records).
- error-event.test.ts: refresh Bun.inspect(ErrorEvent) snapshots for the new
  Node-compat Event inspect.custom output.
…nScope; restore --allow-* ⇒ --permission gate; restore merge-lost fs.write/read arg messages

- permission.rs publish_permission_event called a raw extern-C requireId
  wrapper then global.has_exception(), tripping JSC exception-scope
  validation on ~17 --permission tests. Wrap the call in top_scope! and
  read the exception through it.
- Arguments.rs: re-add the ERR_MISSING_OPTION '--permission is required'
  rejection for --allow-* without --permission (the rejection block was
  removed wholesale along with the obsolete 'not supported' message).
  run-eval.test.ts updated: the model is now implemented, so assert it
  enables process.permission instead of being rejected.
- node_fs.rs: restore the two-stage validateOffsetLengthWrite/validateInt32
  length message and the empty-read-buffer 'Received <inspected>' suffix
  from ac5866b — both hunks were dropped by a later merge.
- test-c-ares.js: sync the null-hostname case with node v26.3.0, which
  removed DEP0118 and rejects an empty hostname.
…warning; refresh Event inspect snapshots

- events.on()'s closeHandler now resets paused=false like node, so next()
  stops calling emitter.resume() after a close event drained the queue —
  readline's resume() throws ERR_USE_AFTER_CLOSE otherwise
  (test-readline-async-iterators.js with the restored 10k-line case).
- http.ts: the ported-lazy debuglog only emits the sensitive-data warning
  on first call; Bun's client never calls it. Call it once when enabled
  so NODE_DEBUG=http still warns (test-http-debug.js).
- inspect.test.js / test-eventtarget.js: Event subclasses now install
  node's [util.inspect.custom] on the prototype. Refresh Bun.inspect
  snapshots for the {type, defaultPrevented, cancelable, timeStamp}
  shape, assert inspecting the bare prototype throws (brand check, like
  node), and drop the Bun-only '[Event]' depth:-1 special-case.
Base automatically changed from claude/callback-throw-uncaught to main August 7, 2026 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants