Skip to content

fix: backport Node.js cleanup hook fixes for node::ObjectWrap addons (45-x-y) - #53391

Draft
claude[bot] wants to merge 7 commits into
45-x-yfrom
fix/node-addon-53387-45-x-y
Draft

fix: backport Node.js cleanup hook fixes for node::ObjectWrap addons (45-x-y)#53391
claude[bot] wants to merge 7 commits into
45-x-yfrom
fix/node-addon-53387-45-x-y

Conversation

@claude

@claude claude Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Requested by Deepak Mohan · Slack thread

Before: On 45-x-y (Node.js 24.20.0) any native addon deriving from node::ObjectWrap that is rebuilt against the current headers aborts the process with Assertion failed: (env) != nullptr in node::RemoveEnvironmentCleanupHook as soon as one of its wrapped objects is garbage-collected while no context is entered (e.g. idle-time GC), because Node.js 24.19.0 backported the change that makes ObjectWrap register a cleanup hook without the follow-up fix that makes removing that hook safe during GC.

After: RemoveEnvironmentCleanupHook() no longer requires a current context and CleanupHookThunkRun() no longer reads the thunk after the hook may have freed it, so node::ObjectWrap addons survive garbage collection and environment teardown.

Description of Change

Backport of #53390

Note: the "Valid Backport" check stays red until #53390 merges, since this manual backport was opened ahead of the original PR.

See that PR for details. It cherry-picks nodejs/node#63985 (68321eff80918e324e9fdaf1aa8cee6db14b84f7) and nodejs/node#65630 at dd2fa31b1d99e5a6710e08ce4139da7c2e344598 / fd325a7fc2b33839c024220aa0c44b9104749686 (not yet landed upstream) into patches/node, and adds the node::ObjectWrap native addon regression spec. Refs #53387.

Validation

Same as #53390: a full Electron build was not possible in the environment this was prepared in, so the patched runtime itself has not been exercised. The patch files are byte-identical to the main PR and were verified to apply cleanly via script/git-import-patches onto nodejs/node v24.20.0 plus the 45-x-y patch set (72 patches), then re-exported with script/git-export-patches with no churn in other patches. The spec was validated through script/spec-runner.js --electronVersion=<v> --skipYarnInstall on Linux x64 against prebuilt releases (no 45-x-y release with a fixed Node.js exists yet): Electron 42.11.0 (Node.js 24.19.0, unfixed) fails with the #53387 assertion, Electron 42.10.1 (Node.js 24.18.1) passes.

Checklist

Release Notes

Notes: Fixed native addons deriving from node::ObjectWrap aborting during garbage collection on Node.js 24.19.0 and later.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NawBg2exXYXTXt12SBGe9U


Generated by Claude Code

Node.js 24.19.0 made node::ObjectWrap register an environment cleanup
hook but did not carry the fix that makes RemoveEnvironmentCleanupHook()
safe to call from ~ObjectWrap() during garbage collection, so any addon
built against the 24.19.0+ headers aborts with
"Assertion failed: (env) != nullptr" once one of its wrapped objects is
collected while no context is entered.

Cherry-picks:
* nodejs/node#63985 "src: keep global list of addon-provided cleanup
  hooks" (68321eff80918e324e9fdaf1aa8cee6db14b84f7 on nodejs/node main).
* nodejs/node#65630 "src: fix use-after-free in CleanupHookThunkRun" at
  dd2fa31b1d99e5a6710e08ce4139da7c2e344598 and its regression cctest at
  fd325a7fc2b33839c024220aa0c44b9104749686 (not yet landed upstream).

Fixes: #53387

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NawBg2exXYXTXt12SBGe9U
Adds a minimal node::ObjectWrap addon fixture with a helper that runs a
full garbage collection while no context is entered, and a spec that
loads it in an ELECTRON_RUN_AS_NODE child process and asserts the
process exits cleanly. Reproduces
#53387 on unfixed builds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NawBg2exXYXTXt12SBGe9U
@trop trop Bot added 45-x-y backport This is a backport PR semver/patch backwards-compatible bug fixes labels Sep 2, 2026
The native addon fixtures under spec/fixtures/native-addon are yarn
workspaces, so yarn only runs their build scripts once per install state.
In CI the root `yarn install` builds them first, against the system
Node.js headers, and the later spec install with npm_config_nodedir set
never rebuilds them. N-API fixtures load anyway, but the object-wrap
fixture uses Node's C++ API, embeds the system NODE_MODULE_VERSION, and
is rejected by Electron with ERR_DLOPEN_FAILED.

Rebuild every native addon fixture with node-gyp after the spec install
so they are compiled against the configured Electron headers, which is
what spec-runner already intended.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NawBg2exXYXTXt12SBGe9U
The rebuild of the native addon fixtures against Electron's node headers
broke every Linux test shard: the CI test image ships GCC 11, which
cannot parse the V8 headers (`class V8_DEPRECATE_SOON(...) V8_EXPORT
Value` mixes a C++11 attribute with a GNU attribute, rejected by GCC
<= 12, see #53284):

  v8config.h:865:20: error: expected identifier before '__attribute__'
  v8-primitive.h:658:4: error: expected '}' before 'public'

Only addons registering through Node's C++ module API (NODE_MODULE /
NODE_MODULE_INIT) embed NODE_MODULE_VERSION and need Electron's headers,
so limit the rebuild to those; the N-API fixtures keep loading fine as
built by the root install. On Linux, when the Chromium toolchain from
the source checkout is present (CI restores third_party/llvm-build and
libc++ into the test job via src_artifacts, and the nan tests already
build with it), compile the fixture with clang and Electron's libc++
exactly as script/nan-spec-runner.js does; otherwise, and for the
--electronVersion prebuilt path, keep using the system compiler.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NawBg2exXYXTXt12SBGe9U
…indows

The Windows test shards failed the fixture rebuild before configure:
node-gyp inherits NPM_CONFIG_MSVS_VERSION=2022 from the test step's env,
but the runners only ship a newer Visual Studio ("msvs_version does not
match this version"). The root install that built the same fixtures ran
without that pin and picked the installed toolset, so strip it from the
rebuild environment and let node-gyp detect Visual Studio. The Electron
import library it links against is already staged by
script/actions/move-artifacts.sh as gen/node_headers/Release/node.lib.

Also replace the source scan for NODE_MODULE usage with an explicit
"electron:requiresElectronHeaders" flag in the fixture's package.json, so
only fixtures that declare they need Electron's headers (currently
object-wrap) are rebuilt and the N-API fixtures are left untouched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NawBg2exXYXTXt12SBGe9U
…ture

The rebuild of spec/fixtures/native-addon/object-wrap against Electron's
headers broke two more sets of Linux test shards:

* linux-arm64: the test job runs on an arm64 host but restores the x64
  Chromium toolchain of the cross-compiling build job, so node-gyp died
  with "Exec format error: .../llvm-build/Release+Asserts/bin/clang".
  The image's GCC is the one that rejects the V8 headers
  (#53284), so nothing on that
  host can compile the fixture. Check that the Chromium clang actually
  runs before selecting it; when it does not, warn loudly, skip the
  rebuild and export ELECTRON_SKIP_ELECTRON_HEADER_ADDON_SPECS so the
  ObjectWrap spec is reported as skipped there (with a comment pointing
  at this gap) instead of failing with an ABI error. Every other platform
  keeps running the spec for real.

* linux-x64-asan / -ubsan: the link failed with "ld.lld: error: unable to
  find library -lc++abi". Sanitizer builds compile libc++abi into the
  electron executable and export it from there
  (export_libcxxabi_from_executables) rather than producing libc++abi.a,
  so only pass -lc++abi when that archive exists; the symbols resolve
  from the executable when the addon is loaded.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NawBg2exXYXTXt12SBGe9U
(cherry picked from commit a723f64)
…kipped

The linux-arm test job runs the specs with --skipYarnInstall, so the spec
install that rebuilds spec/fixtures/native-addon/object-wrap against
Electron's headers never happens there and the ObjectWrap spec failed to
load the fixture the root install built against the system Node.js
headers ("was compiled against a different Node.js version using
NODE_MODULE_VERSION 127. This version of Node.js requires
NODE_MODULE_VERSION 146"). When the install is needed but skipped, warn
and export ELECTRON_SKIP_ELECTRON_HEADER_ADDON_SPECS so those specs are
reported as skipped, as on linux-arm64 where no toolchain can build the
fixture.

Also format the previous change with oxfmt, which `yarn lint:fmt`
flagged in CI.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NawBg2exXYXTXt12SBGe9U
(cherry picked from commit de4cc41)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

45-x-y backport This is a backport PR semver/patch backwards-compatible bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant