{"record":{"id":"c05d6b85e7be5d2a","repo":"denoland/deno","slug":"cannot-register-cleanup-hook-with-same-data-twice","errorCode":null,"errorMessage":"Cannot register cleanup hook with same data twice","messagePattern":"Cannot register cleanup hook with same data twice","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ext/napi/lib.rs","lineNumber":725,"sourceCode":"  pub fn threadsafe_function_ref(&mut self) {\n    self.external_ops_tracker.ref_op();\n  }\n\n  pub fn threadsafe_function_unref(&mut self) {\n    self.external_ops_tracker.unref_op();\n  }\n\n  pub fn add_cleanup_hook(\n    &mut self,\n    hook: napi_cleanup_hook,\n    data: *mut c_void,\n  ) {\n    let mut hooks = self.cleanup_hooks.borrow_mut();\n    if hooks\n      .iter()\n      .any(|pair| std::ptr::fn_addr_eq(pair.0, hook) && pair.1 == data)\n    {\n      panic!(\"Cannot register cleanup hook with same data twice\");\n    }\n    hooks.push((hook, data));\n  }\n\n  pub fn remove_cleanup_hook(\n    &mut self,\n    hook: napi_cleanup_hook,\n    data: *mut c_void,\n  ) {\n    let mut hooks = self.cleanup_hooks.borrow_mut();\n    match hooks\n      .iter()\n      .rposition(|&pair| std::ptr::fn_addr_eq(pair.0, hook) && pair.1 == data)\n    {\n      Some(index) => {\n        hooks.remove(index);\n      }\n      None => panic!(\"Cannot remove cleanup hook which was not registered\"),","sourceCodeStart":707,"sourceCodeEnd":743,"githubUrl":"https://github.com/denoland/deno/blob/f7822238cab635a3a19f99f493f675fa81a7f9d8/ext/napi/lib.rs#L707-L743","documentation":"Node-API's napi_add_cleanup_hook(env, fn, data) lets a native addon run code when the env tears down. Deno tracks registered (function, data) pairs and panics if the exact same pair is registered twice — stricter than some Node builds where duplicates are tolerated. It indicates the addon's registration logic executed more than once for the same hook and data pointer.","triggerScenarios":"A napi addon calling napi_add_cleanup_hook with an identical hook pointer and identical data pointer on every init, while the addon gets instantiated more than once in the same env — double require through two specifiers, duplicate copies on disk, worker-driven re-init.","commonSituations":"Native addons ported from Node where duplicate registration silently no-opped; bundlers/resolvers loading the same .node addon via two paths; version changes in the addon's init guard logic.","solutions":["Guard registration with a static once-flag inside the addon's init","Deduplicate how the addon is loaded — one specifier, one copy on disk","Update the addon — many libraries already fixed double-registration for Deno","If you own the addon, register the hook once per env and derive per-instance data internally"],"exampleFix":"// before — registers on every module init\nnapi_create_object(env, &exports);\nnapi_add_cleanup_hook(env, my_cleanup, data);\n\n// after — register once per env\nstatic bool g_registered = false;\nif (!g_registered) {\n  napi_status s = napi_add_cleanup_hook(env, my_cleanup, data);\n  if (s == napi_ok) g_registered = true;\n}","handlingStrategy":"validation","validationCode":"// C: register the cleanup hook at most once per env\nstatic bool g_registered = false;\nif (!g_registered) {\n  napi_status s = napi_add_cleanup_hook(env, my_cleanup, data);\n  if (s == napi_ok) g_registered = true;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Guard napi_add_cleanup_hook with a once-flag","Load each native addon through a single specifier and a single on-disk copy","Re-test addons after Node-to-Deno migrations — duplicate-registration semantics are stricter here"],"tags":["napi","node-compat","native-addon","cleanup-hook"],"backgroundTag":"napi-cleanup-hook-misuse","analyzedSha":"f7822238cab635a3a19f99f493f675fa81a7f9d8","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}