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

  1. Match the exact (hook, data) pair used at add time — log both pointers if unsure
  2. Remove exactly once: clear your registration state immediately after a successful remove
  3. Audit teardown paths (module unload vs env destroy) so both do not attempt removal
  4. 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

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


AI-assisted analysis of denoland/deno@f7822238ca (2026-08-20). Data as JSON: /api/errors/2e25a90f856367bc. Report an issue: GitHub.