{"record":{"id":"2e25a90f856367bc","repo":"denoland/deno","slug":"cannot-remove-cleanup-hook-which-was-not-registere","errorCode":null,"errorMessage":"Cannot remove cleanup hook which was not registered","messagePattern":"Cannot remove cleanup hook which was not registered","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ext/napi/lib.rs","lineNumber":743,"sourceCode":"      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\"),\n    }\n  }\n\n  pub fn add_ref_finalizer(\n    &self,\n    env: napi_env,\n    cb: napi_finalize,\n    data: *mut c_void,\n    hint: *mut c_void,\n  ) -> NapiFinalizerId {\n    self.ref_tracker.borrow_mut().add(env, cb, data, hint)\n  }\n\n  /// Deregisters a shutdown finalizer entry, returning `true` if it was still\n  /// pending (see [`RefTracker::remove`]).\n  #[must_use = \"the return value decides whether the finalizer may still \\\n                be run\"]\n  pub fn remove_ref_finalizer(&self, id: NapiFinalizerId) -> bool {","sourceCodeStart":725,"sourceCodeEnd":761,"githubUrl":"https://github.com/denoland/deno/blob/f7822238cab635a3a19f99f493f675fa81a7f9d8/ext/napi/lib.rs#L725-L761","documentation":"The counterpart to napi_add_cleanup_hook: Deno panics when napi_remove_cleanup_hook is called with a (function, data) pair that is not currently registered — never added, already removed, or registered with a different data pointer. The lookup uses reverse position search on exact equality of both the function address (fn_addr_eq) and the data pointer, so the pair must match the registration byte-for-byte.","triggerScenarios":"Calling napi_remove_cleanup_hook twice for the same hook; removing with a different data pointer than was registered (typical with per-instance data structs); unregistering on a teardown path after the pair was already removed elsewhere.","commonSituations":"Addons that remove cleanup hooks during module GC and again during env teardown; refactors changing what gets passed as data; code ported from Node where removing an absent hook was ignored.","solutions":["Match the exact (hook, data) pair used at add time — log both pointers if unsure","Remove exactly once: clear your registration state immediately after a successful remove","Audit teardown paths (module unload vs env destroy) so both do not attempt removal","Update the addon or vendor a patch if the double-remove comes from upstream"],"exampleFix":"// before — data pointer differs from registration\nnapi_remove_cleanup_hook(env, my_cleanup, other_data);\n\n// after — remove the exact registered pair, once\nnapi_remove_cleanup_hook(env, my_cleanup, data);","handlingStrategy":"validation","validationCode":"// C: remove only what was registered, exactly once\nif (g_registered) {\n  napi_remove_cleanup_hook(env, my_cleanup, data); // same pair as add\n  g_registered = false;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass the identical (function, data) pair used at registration — the data pointer is compared exactly","Clear registration state immediately after a successful remove","Audit module-unload and env-teardown paths so both never remove the same hook"],"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-14T00:17:10.932Z"}