denoland/deno · error

Inspector deregister handler already exists and is alive.

Error message

Inspector deregister handler already exists and is alive.

What it means

deno_core's inspector supports exactly one deregister handler: add_deregister_handler() hands out a oneshot Receiver that fires when the inspector is dropped, and the slot holds a single sender. Calling add_deregister_handler() again while the previous sender is still present and not canceled panics — the API is designed for one owner of that notification.

Source

Thrown at libs/core/inspector.rs:1237

      let _ = self.state.poll_sessions(None).unwrap();
      std::thread::sleep(std::time::Duration::from_millis(1));
    }
  }

  /// Obtain a sender for proxy channels.
  pub fn get_session_sender(&self) -> UnboundedSender<InspectorSessionProxy> {
    self.new_session_tx.clone()
  }

  /// Create a channel that notifies the frontend when inspector is dropped.
  ///
  /// NOTE: Only a single handler is currently available.
  pub fn add_deregister_handler(&self) -> oneshot::Receiver<()> {
    let maybe_deregister_tx = self.deregister_tx.borrow_mut().take();
    if let Some(deregister_tx) = maybe_deregister_tx
      && !deregister_tx.is_canceled()
    {
      panic!("Inspector deregister handler already exists and is alive.");
    }
    let (tx, rx) = oneshot::channel::<()>();
    self.deregister_tx.borrow_mut().replace(tx);
    rx
  }

  /// Capture an incoming `Network.*` event payload into the shared body buffer
  /// before it is broadcast to sessions. Called from
  /// `op_inspector_emit_protocol_event` so subsequent
  /// `Network.getResponseBody`/`streamResourceContent`/`getRequestPostData`
  /// CDP commands have something to return.
  ///
  /// Returns `true` if the event should still be broadcast to sessions, or
  /// `false` if the event was fully consumed by the buffer. Matches Node's
  /// `NetworkAgent`: `dataSent` is purely a capture event, and `dataReceived`
  /// is only forwarded once `streamResourceContent` has flipped the entry
  /// into streaming mode.
  pub fn capture_network_event(

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Drop (or let fall out of scope) the previous Receiver before requesting another handler
  2. Restructure to register the deregister handler once per inspector and share the receiver
  3. When upgrading deno_core, check the changelog — the lifecycle API may have changed shape
  4. Reproduce in a minimal embedder and report to denoland/deno_core if the constraint seems wrong

Example fix

// before — second live handler panics
let rx1 = inspector.add_deregister_handler();
let rx2 = inspector.add_deregister_handler(); // panic

// after — one handler at a time
let rx1 = inspector.add_deregister_handler();
drop(rx1); // cancel the previous handler first
let rx2 = inspector.add_deregister_handler();
Defensive patterns

Strategy: validation

Validate before calling

// embedder: one live deregister handler at a time
use std::sync::Mutex;
static DEREG_RX: Mutex<Option<oneshot::Receiver<()>>> = Mutex::new(None);
fn replace_deregister_handler(inspector: &Inspector) {
  let mut slot = DEREG_RX.lock().unwrap();
  slot.take(); // drop + cancel the previous handler first
  *slot = Some(inspector.add_deregister_handler());
}

Prevention

When it happens

Trigger: Embedder or runtime glue built on deno_core calls add_deregister_handler() a second time without the first Receiver having been dropped or its sender canceled — e.g. re-creating an inspector client on the same inspector after a session ends.

Common situations: Custom embedders wiring inspector lifecycle events; internal CLI flows around --inspect; deno_core upgrades tightening the single-handler rule so previously-tolerated patterns now panic.

Related errors


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