Repository navigation
chore: upgrade Node.js to v26.13.0 - #55522
Conversation
Keep upstream's simdutf include dir in the node_js2c include_dirs and add "tools" next to it. The patch's FixedArray::Get(context, i) change in node_builtins.cc is no longer needed: upstream made the same change in nodejs/node#61898. Ref: nodejs/node#65382 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Upstream moved the native legacyMainResolve call from esm/resolve.js into the loaderMethods table in internal/modules/helpers.js, so pass the asar-aware fileExists callback from there instead. Ref: nodejs/node#63653 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
…snapshot BuiltinLoader::RefreshCodeCache() now merges with insert_or_assign upstream, so drop that hunk from the patch. Ref: nodejs/node#65352 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
PathHelpersLazyGetter moved from node_modules.cc to module_wrap.cc and gained a sibling ImportMetaResolveLazyGetter; switch both to Holder(). Also follow the DatabaseSyncLimits -> DatabaseLimits rename in node_sqlite.cc (nodejs/node#65988) and the Null(isolate) cleanup in node_constants.cc (nodejs/node#59805). Ref: nodejs/node#60603 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Upstream switched to Get/SetContinuationPreservedEmbedderDataV2(), which Chromium's V8 marks deprecated in favor of the unsuffixed names. Use the unsuffixed getter and setter. Ref: nodejs/node#60174 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
The TRACING_CATEGORY_NODE* macros moved from node_internals.h to tracing/trace_event.h, so define NODE_TRACE_CATEGORY_ENABLED there. Agent::CreateDefault() picks the legacy agent again when V8_USE_PERFETTO is unset (nodejs/node#64053), so only the Perfetto branch is gated on NODE_USE_PERFETTO_SDK. node.gyp now lists the Perfetto sources in node_tracing_perfetto_sources. Add them, with V8's tracing target and config, to the GN libnode target. The removed perfetto backport patch used to carry that wiring. Ref: nodejs/node#64565 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
…permission Upstream added EnvironmentFlags::kNoAddonPermissionForLinkedBindings around the same check. Keep the electron_* exemption alongside it and note in the patch description that the patch can be dropped once Electron sets the new flag. Ref: nodejs/node#66067 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
* support_v8_sandboxed_pointers.patch Ref: nodejs/node#62237 * refactor_attach_cppgc_heap_on_v8_isolate_creation.patch Ref: nodejs/node#58070 * test_accomodate_v8_thenable_stack_trace_change_in_snapshot.patch Ref: nodejs/node#59805 * api_remove_deprecated_getisolate.patch Ref: nodejs/node#59805 Ref: nodejs/node#60223 * api_delete_deprecated_fields_on_v8_isolate.patch Ref: nodejs/node#60488 * api_promote_deprecation_of_v8_context_and_v8_object_api_methods.patch Ref: nodejs/node#60602 * src_refactor_module_wrap_cc_to_update_fixedarray_get_params.patch Ref: nodejs/node#61898 * src_refactor_wasmstreaming_finish_to_accept_a_callback.patch Ref: nodejs/node#61898 * src_stop_using_v8_propertycallbackinfo_t_this.patch Ref: nodejs/node#61898 * fix_add_externalpointertypetag_to_v8_external_api_calls.patch Ref: nodejs/node#61898 * src_stop_using_removed_v8_promise_reject_events.patch Ref: nodejs/node#62336 * src_backport_the_perfetto_trace_agent.patch Ref: nodejs/node#64565 Ref: nodejs/node#64721 * test_account_for_libc_rm_error_on_electron_linux.patch Ref: nodejs/node#66065 * fix_do_not_pass_removed_jspi_v8_flag.patch Ref: nodejs/node#60014 * test_drop_removed_wasm_rab_integration_flag.patch The v26 version of nodejs/node#65845 never added the flag. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Upstream deleted parallel/test-promise-swallowed-event and parallel/test-warn-multipleResolves when the multipleResolves event reached end-of-life. Ref: nodejs/node#58707 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Node.js v25 moved the fs.rmdir recursive option to end-of-life, so
fs.rmdirSync(dir, { recursive: true }) now throws and @types/node v26
no longer accepts the option.
Ref: nodejs/node#58616
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
With @types/node v26, http.IncomingMessage declares typed `on` overloads that do not unify with Electron.IncomingMessage's, so collectStreamBodyBuffer() no longer type-checks when calling `on` on the union. Ref: Unable to locate reference Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
@types/node v26 types webcrypto's BufferSource as views over an ArrayBuffer only, so a Uint8Array<ArrayBufferLike> no longer type-checks. Archive reads never use a SharedArrayBuffer, so narrow the type at the call site. Ref: Unable to locate reference Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Node.js renamed src/tracing/trace_event_common.h to trace_event_legacy.h and moved the legacy trace macros out of trace_event.h into trace_event_legacy_inl.h, so feed both to electron_generate_node_defines instead of the removed header. The generated push/undef set matches the previous one apart from header guards, NODE_TRACE_CATEGORY_ENABLED and INTERNAL_DECLARE_SET_TRACE_VALUE_INT, which Node.js no longer defines. Ref: nodejs/node#64565 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Upstream's trace_event_perfetto.h includes the amalgamated "perfetto.h" SDK header, which Chromium's Perfetto does not provide, so libnode fails to compile. Include the Perfetto public headers Chromium ships instead (as the removed backport patch did), add the tracing/trace_config includes agent_perfetto.h relied on, and skip Node's TrackEvent category definitions when Chromium's built-in categories are already in scope. Ref: nodejs/node#64565 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Upstream added indexed interceptors for process.env. Migrate the new EnvSetterIndexed and EnvDefinerIndexed callbacks to PropertyCallbackInfo<Boolean> like their named counterparts, since Chromium's V8 only accepts the Boolean-returning indexed setter and definer callbacks. Ref: nodejs/node#60826 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Since Node.js 26, readable.read() returns one buffered chunk at a time instead of concatenating everything buffered, so takeHeapSnapshot() parsed only the first 64 KiB of the snapshot. Read readableLength bytes, as upstream test/common/heap.js now does. Ref: nodejs/node#60441 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Node.js 26 throws ERR_MISSING_OPTION('--permission') when a boolean
permission flag such as --allow-addons is set without --permission
(previously only the fs flags were checked), so the utility process in
this fixture exited before reaching options.js. Repeat --trace-warnings
instead; the case still checks that disallowed NODE_OPTIONS are stripped
from a list with repeated options.
Ref: nodejs/node#62072
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
The usertiming.js hunk that emits node.perf.usertiming trace events for performance.mark() and performance.measure() lived in the perfetto backport patch, which was removed as upstreamed. Upstream Node.js never had it (Electron added it in #50591), so carry it in fix_use_embedder_perfetto_client_when_available.patch. Ref: #50591 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Since Node.js 26, node::Buffer::New() copies memory allocated outside the V8 sandbox into it instead of handing it to v8::ArrayBuffer::NewBackingStore(), so the external-ab addon no longer crashed the utility process and "emits 'error' when fatal error is triggered from V8" timed out. Call NewBackingStore() from the addon so the same v8_ArrayBuffer_NewBackingStore fatal error is still raised. Ref: nodejs/node#62237 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Node.js 26 removed the recursive option of fs.rmdir(), fs.rmdirSync() and fsPromises.rmdir() (DEP0147), so they now throw ERR_INVALID_ARG_VALUE. Use rmSync() and promises.rm(), and put an archive in the directory so the tests delete what their titles say. Ref: nodejs/node#58616 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Node.js 26 adds a Web Worker API and its pre-execution deletes globalThis.Worker unless --experimental-web-worker is set, without checking noBrowserGlobals, so renderers and web workers with Node integration lost Blink's Worker. Pass the flag in those processes, as is already done for --experimental-eventsource; Node's own Worker is not installed under kNoBrowserGlobals. Ref: nodejs/node#64894 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Node.js 26 removed the flag, so passing it now fails with "bad option" (exit code 9). Stop forwarding it to Node.js, drop it from the command-line switch docs, document the removal as a 46.0 breaking change, and remove the spec and fixture that exercised it. The spec that checks TypeScript-only syntax is not transformed stays. Ref: nodejs/node#61803 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Node.js now passes --external-memory-max-reasonable-size=0 to V8 in every process that initializes it, which changes FlagList::Hash() and so the code-cache key. Add the flag to the build-time js2c code cache flavors that initialize Node.js and to electron_xcache's default flags, so their caches keep being accepted (xcache.spec.ts failed on the flag hash). Ref: nodejs/node#65589 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Node.js 26 writes files (fs.writeFile, fs.appendFile and their fsPromises versions) with a native WriteFileJob, and copies directory trees for fs.cp() / fsPromises.cp() with a native CpDirJob, instead of going through binding.open() and the fs.promises walk the asar wrapper already handles. Writes into archives therefore failed with ENOTDIR instead of EACCES (and missed unpacked entries), and fs.cp() of an archive directory failed with ENOTDIR. Give binding.WriteFileJob the same archive checks as open(), wrap fs.writeFile() for archive paths since lib/fs.js captured the native job at startup, and copy archive trees for CpDirJob with the walk cpSync() already uses, which now also stamps directories with preserveTimestamps like the native walk. Ref: nodejs/node#65489 Ref: nodejs/node#65488 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
The NodePlatform hunk that owns the v8::TracingController it creates when the embedder passes none lived in the perfetto backport patch, which was removed as upstreamed, but upstream still allocates it with a bare new. With the perfetto agent, ELECTRON_RUN_AS_NODE passes no controller, so LeakSanitizer failed every run-as-node spec on ASan builds. Carry the hunk in fix_use_embedder_perfetto_client_when_available.patch. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
…ding The utilityProcess 'error' event from #43774 reports any V8 fatal error. Its spec raised one with the external-ab native addon, which handed V8 memory from outside the sandbox. Node.js 26 makes every Buffer site sandbox compatible, so that no longer happens through Node.js APIs. The earlier commits on this branch made the addon call V8 directly, and that needed ABI workarounds on Windows and for libc++. As Deepak suggested in review, drop the addon and its workspace entries. Add triggerV8FatalError() to the electron_common_testing binding, which is only built when DCHECKs are on. It fails a V8 API check by setting an internal field on a plain object, so it does not depend on the V8 sandbox. The spec keeps #43774's checks on the event type, location and report trigger, also checks the report's event message, and now runs wherever the testing binding exists, including Windows on Arm. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
events.once(emitter, 'exit') also listens for 'error' and rejects with its first argument. The previous commit started waiting for 'exit' before the 'error' event arrived, so the utility process's 'FatalError' rejected that promise and the spec failed with "Unknown Error: FatalError". Wait for 'exit' after 'error' has been received, as the spec from #43774 did. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
…dbox Following Deepak's review, replace the sandbox-page fallback added for nodejs/node#61480 and the older InSandboxAllocator with a single PAInSandboxAllocator, modelled on d8's. nodejs/node#61480 moved Buffer.allocUnsafe() onto the isolate's ArrayBuffer allocator. gin's partition caps a single allocation at partition_alloc::MaxAllocationSize() (2 GiB - 2 MiB), so Buffers above that size failed. The previous fix served those requests from whole sandbox pages and tracked them in a separate map. PAInSandboxAllocator owns a PartitionAlloc partition in the configurable pool, which gin::V8Initializer::Initialize() places inside the V8 sandbox. The partition is created lazily, after the pool exists. It has allow_giga_allocations set and allocates with kAllowGigaAllocations, along with kNoOverrideHooks and kNoMemoryToolOverride, as gin does. A single ArrayBuffer can therefore reach MaxGigaAllocationSize() (16 GiB - 2 MiB on 64-bit) without the page fallback. It is both the ArrayBuffer allocator for JavascriptEnvironment isolates and V8's in-sandbox allocator, so Node.js worker_threads isolates use it through NewDefaultAllocator(). Builds without the V8 sandbox keep gin's allocator. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
|
@deepak1556 @ckerr this is #55418's branch unchanged at f95dc02 (the roller closed that PR and reset roller/node/main). Your earlier reviews there still apply. Generated by Claude Code |
|
Release note looks good now. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
| } | ||
| }; |
There was a problem hiding this comment.
🟡 nit (optional): Callers of fs.promises.stat or fs.promises.lstat with a bad URL now get a synchronous throw, not a rejected promise. The new wrappers are plain functions that call splitPath() before any promise exists. For a URL, splitPath() calls getValidatedPath(), which throws on a non-file scheme. Fix: make both wrappers async, or wrap the splitPath() call so it returns Promise.reject(error), so every input error rejects as it does on the base branch.
Why this was flagged
The trigger is a call like fs.promises.stat(new URL('https://example.com/x')).catch(handler), or a file: URL that getValidatedPath() rejects, such as an encoded slash on POSIX. The new wrappers at lib/node/asar-fs-wrapper.ts:33 and :47 call splitPath(pathArgument) directly. splitPath at lib/node/asar-fs-wrapper.ts:116-117 calls getValidatedPath(archivePath) for any NodeURL, and that throws synchronously. The .catch() handler never runs, and the exception reaches the caller as a synchronous throw. On the base branch, fs.promises.stat was util.promisify(fs.stat), and fs.stat ran inside the promise executor, so the same input gave a rejected promise. Node's own fsPromises.stat is an async function and always rejects. No guard catches this, because the validation happens before any promise is created.
Verification: For a NodeURL, splitPath (lib/node/asar-fs-wrapper.ts:116-117) calls archivePath = getValidatedPath(archivePath) synchronously. On the base branch, util.promisify called fs.stat/fs.lstat inside the Promise executor, so the same throw became a rejected promise. After this change the error escapes synchronously and a .catch() handler never runs.
There was a problem hiding this comment.
Fixed in 5b36578: the stat/lstat wrappers are now async, so an invalid URL rejects instead of throwing.
Generated by Claude Code
The new fs.promises.stat/lstat wrappers call splitPath() before deciding whether to use fsPromises' own implementation. For a URL, splitPath() runs getValidatedPath(), which throws for a non-file: URL, so the error escaped synchronously instead of rejecting the returned promise as it did with util.promisify(fs.stat) on main and as Node's fsPromises does. Make the wrappers async so every input error rejects, and add a spec for it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
deepak1556
left a comment
There was a problem hiding this comment.
LGTM on allocator, perfetto and external-ab addon changes. Haven't reviewed asar-wrapper changes.
|
API LGTM |
|
Release Notes Persisted
|
|
I have automatically backported this PR to "46-x-y", please check out #55527 |
Requested by John Kleinschmidt · Slack thread
Upgrades Node.js on main from v24.22.1 to v26.13.0. Of the 55 Node.js patches, 15 are removed because v26.13.0 already includes them, and the rest are adapted to upstream changes. This replaces #55418, which the roller closed and reset; review history is there.
Some changes need a closer look from reviewers. Node.js 26 allocates
Buffer.allocUnsafe()memory through the isolate's ArrayBuffer allocator (nodejs/node#61480). Electron now gives the main process, utility process andELECTRON_RUN_AS_NODEisolates their own allocator, so Buffers larger than 2 GiB keep working.ELECTRON_RUN_AS_NODEnow honors--no-node-snapshot. Renderers and workers are started with--experimental-web-worker. The--experimental-transform-typesswitch is removed, because Node.js removed it, and the change is documented in the breaking-changes doc. The asar fs wrapper now covers Node.js 26's native write and copy jobs and its virtual file system. The trace_events, source-phase-import and process-timeout report tests are disabled for Electron-specific reasons, which are given in their commits.🤖 Generated with Claude Code
https://claude.ai/code/session_01Vf2yMuJ7XxvNktpKuiMPzU
Notes: Upgraded to Node.js v26.13.0. Removed the
--experimental-transform-typesswitch, which Node.js no longer supports.