denoland/deno · error

Cannot register cleanup hook with same data twice

Error message

Cannot register cleanup hook with same data twice

What it means

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.

Source

Thrown at ext/napi/lib.rs:725

  pub fn threadsafe_function_ref(&mut self) {
    self.external_ops_tracker.ref_op();
  }

  pub fn threadsafe_function_unref(&mut self) {
    self.external_ops_tracker.unref_op();
  }

  pub fn add_cleanup_hook(
    &mut self,
    hook: napi_cleanup_hook,
    data: *mut c_void,
  ) {
    let mut hooks = self.cleanup_hooks.borrow_mut();
    if hooks
      .iter()
      .any(|pair| std::ptr::fn_addr_eq(pair.0, hook) && pair.1 == data)
    {
      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"),

View on GitHub (pinned to f7822238ca)

Solutions

  1. Guard registration with a static once-flag inside the addon's init
  2. Deduplicate how the addon is loaded — one specifier, one copy on disk
  3. Update the addon — many libraries already fixed double-registration for Deno
  4. If you own the addon, register the hook once per env and derive per-instance data internally

Example fix

// before — registers on every module init
napi_create_object(env, &exports);
napi_add_cleanup_hook(env, my_cleanup, data);

// after — register once per env
static bool g_registered = false;
if (!g_registered) {
  napi_status s = napi_add_cleanup_hook(env, my_cleanup, data);
  if (s == napi_ok) g_registered = true;
}
Defensive patterns

Strategy: validation

Validate before calling

// C: register the cleanup hook at most once per env
static bool g_registered = false;
if (!g_registered) {
  napi_status s = napi_add_cleanup_hook(env, my_cleanup, data);
  if (s == napi_ok) g_registered = true;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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