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
Reproduced on: Darwin 25.5.0 arm64 (official darwin-arm64 tarballs)
Originally hit on: Linux x86_64, Debian 13 container
Subsystem
src, addons
What steps will reproduce the bug?
This is the 24.x counterpart of #65262, but with an important difference: on the 24.x line
the crash does not need a header/runtime version mismatch. Headers and runtime both at
24.19.0 abort deterministically.
const{ Thing }=require(process.argv[2]);letjunk=[];for(leti=0;i<300000;i++){newThing();// wrapped object, immediately unreachablejunk.push({a: i});// keep allocation rate high so V8 collects on its ownif(junk.length>1000)junk=[];}console.log('survived');
Build against each header set and run each binary on each runtime:
How often does it reproduce? Is there a required condition?
Deterministic, 5/5 runs for both crashing combinations.
hdr \ rt
24.18.1
24.19.0
24.18.1
ok
ok
24.19.0
crash
crash
Compare with the 26.x matrix in #65262, where headers 26.4.0 on runtime 26.4.0 is fine.
As in that issue, an explicit global.gc() does not trigger it; the collection has to be
allocation driven.
What is the expected behavior? Why is that the expected behavior?
An addon that uses node::ObjectWrap and is built against 24.19.0 headers should keep
working on a 24.19.0 runtime. Today there is no 24.x runtime it works on.
The cause looks like a partial backport. 24.19.0 picked up src: add cleanup hooks to node::ObjectWrap (#63642), which adds AddCleanupHook() to the constructor and RemoveCleanupHook() to the destructor of the header only node::ObjectWrap class. It did not pick up 1723773d4133fc71215a9cbb7e2b9a2a11fc3688 (src: keep global list of addon-provided cleanup hooks), which is what makes hook removal survive without a live Environment and which is why the 26.x matrix in #65262 has a working diagonal.
v24.x-staging is currently in the same state, and v24.19.0 is the newest v24 tag, so
every published 24.x runtime is affected once an addon is compiled with 24.19.0 headers.
Because node_object_wrap.h is header only, this reaches users who never changed a
dependency: rebuilding a container image after 2026-08-03 is enough, since the new
destructor code gets compiled into the addon. It hits NAN style addons in particular,
because they commonly do using namespace node; and inherit from node::ObjectWrap.
Real world example that led me here: a long running service in a Docker image, no
dependency version changed, only the Node base image moved 24.18.0 -> 24.19.0. The process
started aborting during periodic SFTP transfers, when the garbage collector reclaimed one
of ssh2's native cipher objects:
Downstream reports of the same assertion with other NAN style addons (better-sqlite3, node-pty) after the same base image move: nexu-io/open-design#6462.
Possible ways out, in order of preference from a user point of view:
Backport 1723773d to v24.x-staging so RemoveEnvironmentCleanupHook no longer needs
a live Environment.
Version
v24.19.0 (also affects
v24.x-staging)Platform
Subsystem
src, addons
What steps will reproduce the bug?
This is the 24.x counterpart of #65262, but with an important difference: on the 24.x line
the crash does not need a header/runtime version mismatch. Headers and runtime both at
24.19.0 abort deterministically.
addon.cc(same minimal reproducer as #65262):index.js:Build against each header set and run each binary on each runtime:
How often does it reproduce? Is there a required condition?
Deterministic, 5/5 runs for both crashing combinations.
Compare with the 26.x matrix in #65262, where headers 26.4.0 on runtime 26.4.0 is fine.
As in that issue, an explicit
global.gc()does not trigger it; the collection has to beallocation driven.
What is the expected behavior? Why is that the expected behavior?
An addon that uses
node::ObjectWrapand is built against 24.19.0 headers should keepworking on a 24.19.0 runtime. Today there is no 24.x runtime it works on.
What do you see instead?
Additional information
The cause looks like a partial backport. 24.19.0 picked up
src: add cleanup hooks to node::ObjectWrap(#63642), which addsAddCleanupHook()to the constructor andRemoveCleanupHook()to the destructor of the header onlynode::ObjectWrapclass. It didnot pick up
1723773d4133fc71215a9cbb7e2b9a2a11fc3688(src: keep global list of addon-provided cleanup hooks), which is what makes hook removal survive without a liveEnvironmentand which is why the 26.x matrix in #65262 has a working diagonal.v24.x-stagingis currently in the same state, and v24.19.0 is the newest v24 tag, soevery published 24.x runtime is affected once an addon is compiled with 24.19.0 headers.
Because
node_object_wrap.his header only, this reaches users who never changed adependency: rebuilding a container image after 2026-08-03 is enough, since the new
destructor code gets compiled into the addon. It hits NAN style addons in particular,
because they commonly do
using namespace node;and inherit fromnode::ObjectWrap.Real world example that led me here: a long running service in a Docker image, no
dependency version changed, only the Node base image moved 24.18.0 -> 24.19.0. The process
started aborting during periodic SFTP transfers, when the garbage collector reclaimed one
of
ssh2's native cipher objects:Downstream reports of the same assertion with other NAN style addons (
better-sqlite3,node-pty) after the same base image move: nexu-io/open-design#6462.Possible ways out, in order of preference from a user point of view:
1723773dtov24.x-stagingsoRemoveEnvironmentCleanupHookno longer needsa live
Environment.node::ObjectWrap#63642 on the 24.x line until 1 is done.Refs: #63642 #65262 #65195