Title: 24.19.0: `node::ObjectWrap` cleanup hooks backported without the cleanup hook registry, aborts on every 24.x runtime · Issue #65446 · nodejs/node · GitHub
Open Graph Title: 24.19.0: `node::ObjectWrap` cleanup hooks backported without the cleanup hook registry, aborts on every 24.x runtime · Issue #65446 · nodejs/node
X Title: 24.19.0: `node::ObjectWrap` cleanup hooks backported without the cleanup hook registry, aborts on every 24.x runtime · Issue #65446 · nodejs/node
Description: Version v24.19.0 (also affects v24.x-staging) Platform 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...
Open Graph Description: Version v24.19.0 (also affects v24.x-staging) Platform Reproduced on: Darwin 25.5.0 arm64 (official darwin-arm64 tarballs) Originally hit on: Linux x86_64, Debian 13 container Subsystem src, addons...
X Description: Version v24.19.0 (also affects v24.x-staging) Platform Reproduced on: Darwin 25.5.0 arm64 (official darwin-arm64 tarballs) Originally hit on: Linux x86_64, Debian 13 container Subsystem src, addons...
Opengraph URL: https://github.com/nodejs/node/issues/65446
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"24.19.0: `node::ObjectWrap` cleanup hooks backported without the cleanup hook registry, aborts on every 24.x runtime","articleBody":"### Version\n\nv24.19.0 (also affects `v24.x-staging`)\n\n### Platform\n\n```text\nReproduced on: Darwin 25.5.0 arm64 (official darwin-arm64 tarballs)\nOriginally hit on: Linux x86_64, Debian 13 container\n```\n\n### Subsystem\n\nsrc, addons\n\n### What steps will reproduce the bug?\n\nThis is the 24.x counterpart of #65262, but with an important difference: on the 24.x line\nthe crash does **not** need a header/runtime version mismatch. Headers and runtime both at\n24.19.0 abort deterministically.\n\n`addon.cc` (same minimal reproducer as #65262):\n\n```c++\n#include \u003cnode.h>\n#include \u003cnode_object_wrap.h>\n\nusing namespace v8;\n\nclass Thing : public node::ObjectWrap {\n public:\n static void New(const FunctionCallbackInfo\u003cValue>& args) {\n (new Thing())->Wrap(args.This());\n }\n};\n\nvoid Init(Local\u003cObject> exports) {\n Isolate* isolate = Isolate::GetCurrent();\n Local\u003cContext> context = isolate->GetCurrentContext();\n Local\u003cFunctionTemplate> tpl = FunctionTemplate::New(isolate, Thing::New);\n tpl->InstanceTemplate()->SetInternalFieldCount(1);\n exports->Set(context,\n String::NewFromUtf8(isolate, \"Thing\").ToLocalChecked(),\n tpl->GetFunction(context).ToLocalChecked()).Check();\n}\n\nNODE_MODULE(NODE_GYP_MODULE_NAME, Init)\n```\n\n`index.js`:\n\n```javascript\nconst { Thing } = require(process.argv[2]);\n\nlet junk = [];\nfor (let i = 0; i \u003c 300000; i++) {\n new Thing(); // wrapped object, immediately unreachable\n junk.push({ a: i }); // keep allocation rate high so V8 collects on its own\n if (junk.length > 1000) junk = [];\n}\nconsole.log('survived');\n```\n\nBuild against each header set and run each binary on each runtime:\n\n```sh\nfor V in 24.18.1 24.19.0; do\n curl -sfLO \"https://nodejs.org/dist/v$V/node-v$V-darwin-arm64.tar.gz\"\n tar -xzf \"node-v$V-darwin-arm64.tar.gz\"\n clang++ -std=c++20 -fPIC -shared -Wl,-undefined,dynamic_lookup \\\n -I\"node-v$V-darwin-arm64/include/node\" \\\n -DNODE_GYP_MODULE_NAME=addon -o \"addon-hdr-$V.node\" addon.cc\ndone\n\nfor HV in 24.18.1 24.19.0; do\n for RV in 24.18.1 24.19.0; do\n ./node-v$RV-darwin-arm64/bin/node index.js \"./addon-hdr-$HV.node\"\n echo \"hdr $HV / rt $RV -> rc=$?\"\n done\ndone\n```\n\n### How often does it reproduce? Is there a required condition?\n\nDeterministic, 5/5 runs for both crashing combinations.\n\n| hdr \\ rt | 24.18.1 | 24.19.0 |\n|----------|---------|---------|\n| 24.18.1 | ok | ok |\n| 24.19.0 | crash | crash |\n\nCompare with the 26.x matrix in #65262, where headers 26.4.0 on runtime 26.4.0 is fine.\nAs in that issue, an explicit `global.gc()` does not trigger it; the collection has to be\nallocation driven.\n\n### What is the expected behavior? Why is that the expected behavior?\n\nAn addon that uses `node::ObjectWrap` and is built against 24.19.0 headers should keep\nworking on a 24.19.0 runtime. Today there is no 24.x runtime it works on.\n\n### What do you see instead?\n\n```\n# node[52504]: void node::RemoveEnvironmentCleanupHook(Isolate *, CleanupHook, void *) at ../src/api/hooks.cc:142\n# Assertion failed: (env) != nullptr\n\n1: node::Assert(node::AssertionInfo const&) [node]\n2: node::RemoveEnvironmentCleanupHook(v8::Isolate*, void (*)(void*), void*) (.cold.1) [node]\n4: node::ObjectWrap::RemoveCleanupHook() [addon-hdr-24.19.0.node]\n5: node::ObjectWrap::~ObjectWrap() [addon-hdr-24.19.0.node]\n```\n\n### Additional information\n\nThe cause looks like a partial backport. 24.19.0 picked up `src: add cleanup hooks to\nnode::ObjectWrap` (#63642), which adds `AddCleanupHook()` to the constructor and\n`RemoveCleanupHook()` to the destructor of the header only `node::ObjectWrap` class. It did\n**not** pick up `1723773d4133fc71215a9cbb7e2b9a2a11fc3688` (`src: keep global list of\naddon-provided cleanup hooks`), which is what makes hook removal survive without a live\n`Environment` and which is why the 26.x matrix in #65262 has a working diagonal.\n\n```console\n$ curl -s https://raw.githubusercontent.com/nodejs/node/v24.19.0/src/node_object_wrap.h | grep -c 'RemoveCleanupHook()'\n2\n$ curl -s https://raw.githubusercontent.com/nodejs/node/v24.19.0/src/api/hooks.cc | grep -c CleanupHookThunk\n0\n$ curl -s https://raw.githubusercontent.com/nodejs/node/v26.4.0/src/api/hooks.cc | grep -c CleanupHookThunk\n6\n```\n\n`v24.x-staging` is currently in the same state, and v24.19.0 is the newest v24 tag, so\nevery published 24.x runtime is affected once an addon is compiled with 24.19.0 headers.\n\nBecause `node_object_wrap.h` is header only, this reaches users who never changed a\ndependency: rebuilding a container image after 2026-08-03 is enough, since the new\ndestructor code gets compiled into the addon. It hits NAN style addons in particular,\nbecause they commonly do `using namespace node;` and inherit from `node::ObjectWrap`.\n\nReal world example that led me here: a long running service in a Docker image, no\ndependency version changed, only the Node base image moved 24.18.0 -> 24.19.0. The process\nstarted aborting during periodic SFTP transfers, when the garbage collector reclaimed one\nof `ssh2`'s native cipher objects:\n\n```\n# node[7]: void node::RemoveEnvironmentCleanupHook(v8::Isolate*, CleanupHook, void*) at ../../src/api/hooks.cc:142\n# Assertion failed: (env) != nullptr\n\n1: node::Assert(node::AssertionInfo const&) [node]\n2: node::RemoveEnvironmentCleanupHook(v8::Isolate*, void (*)(void*), void*) [node]\n3: AESGCMDecipher::~AESGCMDecipher() [\u003capp>/node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node]\n```\n\nDownstream reports of the same assertion with other NAN style addons (`better-sqlite3`,\n`node-pty`) after the same base image move: nexu-io/open-design#6462.\n\nPossible ways out, in order of preference from a user point of view:\n\n1. Backport `1723773d` to `v24.x-staging` so `RemoveEnvironmentCleanupHook` no longer needs\n a live `Environment`.\n2. Or revert #63642 on the 24.x line until 1 is done.\n\nRefs: #63642 #65262 #65195\n","author":{"url":"https://github.com/roy-trgscreen","@type":"Person","name":"roy-trgscreen"},"datePublished":"2026-08-20T21:26:03Z","url":"https://github.com/nodejs/node/issues/65446"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:1f44979a-6178-12d2-04e9-89550f764b50 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | C776:273AB2:3BAB61:4E1A53:6AED2117 |
| html-safe-nonce | f9b00ffbcf72991a5148f29f2d8a67158144105bc333261a8d2401388f98f8b1 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDNzc2OjI3M0FCMjozQkFCNjE6NEUxQTUzOjZBRUQyMTE3IiwidmlzaXRvcl9pZCI6IjUyMzgxNzg5MDY2ODUzODI5MzUiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 8c99503d116f42a826b81028549a58b035ac8c8f13e0395114129979198cd24c |
| hovercard-subject-tag | issue:5208018647 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/65446/issue_layout |
| twitter:image | https://opengraph.githubassets.com/631aa414d1438b1f603a992943e134ac14f62894cca319363346a074c181fadc/nodejs/node/issues/65446 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/631aa414d1438b1f603a992943e134ac14f62894cca319363346a074c181fadc/nodejs/node/issues/65446 |
| og:image:alt | Version v24.19.0 (also affects v24.x-staging) Platform Reproduced on: Darwin 25.5.0 arm64 (official darwin-arm64 tarballs) Originally hit on: Linux x86_64, Debian 13 container Subsystem src, addons... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | roy-trgscreen |
| hostname | github.com |
| expected-hostname | github.com |
| None | e3e0f7f63e50c6fe879198a93a0a023a648ecc0a5e9bc3b5e033ac57172237e6 |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 707a24b346ac8a82878b9c084efaa38a997660cb |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width