denoland/deno · error

Inspector UUID already in map

Error message

Inspector UUID already in map

What it means

The inspector server keeps a HashMap<Uuid, InspectorInfo>; each inspector is created with Uuid::new_v4 (inspector_server.rs:516) and registered over a channel, and the server task panics if the uuid is already in the map. Because v4 UUIDs are random, a real hit almost always means duplicate registration rather than chance: the same InspectorInfo was sent twice, or a fork() duplicated process state so a child re-registers a colliding identity.

Source

Thrown at libs/dcore/src/inspector_server.rs:441

    _ = server_handler => {},
  }
}

async fn listen_for_new_inspectors(
  mut register_inspector_rx: UnboundedReceiver<InspectorInfo>,
  inspector_map: Rc<RefCell<HashMap<Uuid, InspectorInfo>>>,
) {
  while let Some(info) = register_inspector_rx.next().await {
    eprintln!(
      "Debugger listening on {}",
      info.get_websocket_debugger_url(&info.host.to_string())
    );
    eprintln!("Visit chrome://inspect to connect to the debugger.");
    if info.wait_for_session {
      eprintln!("Deno is waiting for debugger to connect.");
    }
    if inspector_map.borrow_mut().insert(info.uuid, info).is_some() {
      panic!("Inspector UUID already in map");
    }
  }
}

/// The pump future takes care of forwarding messages between the websocket
/// and channels. It resolves when either side disconnects, ignoring any
/// errors.
///
/// The future proxies messages sent and received on a WebSocket
/// to a UnboundedSender/UnboundedReceiver pair. We need these "unbounded" channel ends to sidestep
/// Tokio's task budget, which causes issues when JsRuntimeInspector::poll_sessions()
/// needs to block the thread because JavaScript execution is paused.
///
/// This works because UnboundedSender/UnboundedReceiver are implemented in the
/// 'futures' crate, therefore they can't participate in Tokio's cooperative
/// task yielding.
async fn pump_websocket_messages(
  mut websocket: WebSocket<TokioIo<hyper::upgrade::Upgraded>>,

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Create and register each inspector exactly once per process; construct a fresh InspectorInfo (new Uuid::new_v4) for every registration
  2. Call fork() before any inspector/server is created, never after
  3. Deduplicate registration logic so only one code path sends InspectorInfo to the server
  4. If supervising multiple runtimes, ensure each gets its own inspector session rather than sharing a handle

Example fix

// before: fork after the inspector exists; child re-registers same uuid
let server = InspectorServer::new(addr, "main")?; // inspector created
let pid = unsafe { libc::fork() }; // child later panics: 'Inspector UUID already in map'

// after: fork first, then create inspector/server per process
let pid = unsafe { libc::fork() };
let server = InspectorServer::new(addr, if pid == 0 { "child" } else { "main" })?;
Defensive patterns

Strategy: validation

Validate before calling

// In embeddings: register each inspector exactly once, keyed by runtime id.
let registered: HashSet<Uuid> = HashSet::new();
assert!(registered.insert(info.uuid.clone()),
  "inspector {uuid} already registered", uuid = info.uuid);

Prevention

When it happens

Trigger: An embedding manually re-sending an InspectorInfo registration over the channel; fork()ing a process after an inspector was created so parent and child both register; a custom runtime harness that clones and re-registers inspector handles when spawning workers.

Common situations: Prefork servers or test harnesses that fork after enabling --inspect; embeddings that build their own InspectorInfo instead of letting each runtime construct a fresh one; duplicated initialization code paths in multi-worker setups.

Related errors


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