denoland/deno · error
Cannot remove cleanup hook which was not registered
Error message
Cannot remove cleanup hook which was not registered
What it means
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.
Source
Thrown at ext/napi/lib.rs:743
panic!("Cannot register cleanup hook with same data twice");
}
hooks.push((hook, data));
}
pub fn remove_cleanup_hook(
&mut self,
hook: napi_cleanup_hook,
data: *mut c_void,
) {
let mut hooks = self.cleanup_hooks.borrow_mut();
match hooks
.iter()
.rposition(|&pair| std::ptr::fn_addr_eq(pair.0, hook) && pair.1 == data)
{
Some(index) => {
hooks.remove(index);
}
None => panic!("Cannot remove cleanup hook which was not registered"),
}
}
pub fn add_ref_finalizer(
&self,
env: napi_env,
cb: napi_finalize,
data: *mut c_void,
hint: *mut c_void,
) -> NapiFinalizerId {
self.ref_tracker.borrow_mut().add(env, cb, data, hint)
}
/// Deregisters a shutdown finalizer entry, returning `true` if it was still
/// pending (see [`RefTracker::remove`]).
#[must_use = "the return value decides whether the finalizer may still \
be run"]
pub fn remove_ref_finalizer(&self, id: NapiFinalizerId) -> bool {View on GitHub (pinned to f7822238ca)
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
Example fix
// before — data pointer differs from registration napi_remove_cleanup_hook(env, my_cleanup, other_data); // after — remove the exact registered pair, once napi_remove_cleanup_hook(env, my_cleanup, data);
Defensive patterns
Strategy: validation
Validate before calling
// C: remove only what was registered, exactly once
if (g_registered) {
napi_remove_cleanup_hook(env, my_cleanup, data); // same pair as add
g_registered = false;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cannot register cleanup hook with same data twice
- Using cpu-features module is currently not supported
- Empty filepath.
- resolve hook must return { shortCircuit: true } or call next
- load hook must return { shortCircuit: true } or call nextLoa
AI-assisted analysis of denoland/deno@f7822238ca (2026-08-20).
Data as JSON: /api/errors/2e25a90f856367bc.
Report an issue: GitHub.