Kuberwastaken/claurst · error

CDP error

Error message

CDP error: {}

What it means

Chrome replied to a CDP command with a JSON-RPC-style error object instead of a result. cdp_call detects val["error"] for the matching request id and surfaces the entire error value (method, message, data) via this anyhow message. The library itself is fine; Chrome rejected the command.

Solutions

  1. Read the embedded CDP error detail in the message; it names the failing method and reason.
  2. Re-query the DOM/target after navigation instead of reusing stale node ids or selectors.
  3. Connect to a page target rather than the browser/service-worker target for page commands.
  4. Check Chrome version compatibility for the CDP method being used.

Example fix

// before
let val: Value = serde_json::from_str(&text)?;
if val["id"] == id {
    if let Some(err) = val.get("error") {
        return Err(anyhow::anyhow!("CDP error: {}", err));
    }
    return Ok(val);
}
// after
// re-acquire fresh node references after any navigation before retrying DOM commands
let document = cdp_call(ws, "DOM.getDocument", json!({})).await?;
let node = cdp_call(ws, "DOM.querySelector", json!({"nodeId": document["result"]["root"]["nodeId"], "selector": sel})).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// use a browser target (type=page) for page-level CDP commands
let targets: Vec<Value> = reqwest::get("http://localhost:9222/json").await?.json().await?;
assert!(targets.iter().any(|t| t["type"] == "page"));

Type guard

fn is_cdp_error(val: &serde_json::Value) -> bool {
    val.get("error").is_some()
}

Try / catch

// match on the CDP error code for targeted recovery
if let Some(err) = val.get("error") {
    if err["message"].as_str().unwrap_or("").contains("No node with given id") {
        return Err(anyhow!("stale node id: re-run DOM.getDocument"));
    }
    return Err(anyhow!("CDP error: {}", err));
}

Prevention

When it happens

Trigger: In cdp_call, the response with the matching id contains an "error" key: e.g. CDP commands issued to the wrong target type, invalid parameters, "No node with given id" from stale DOM references, or Page.navigate to an unreachable URL.

Common situations: Calling Page/DOM commands on a target that doesn't support them (e.g. service worker or browser target); acting on a DOM node removed by a prior navigation; invalid selector or node id; protocol version mismatch between the client and Chrome.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/d699bf5fbee079f3. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/commands/src/chrome.rs:82

        // Drain messages until we get the one with our id (ignore events).
        loop {
            let raw = ws
                .next()
                .await
                .ok_or_else(|| anyhow::anyhow!("WebSocket closed unexpectedly"))??;
            let text: String = match raw {
                WsMessage::Text(t) => t.to_string(),
                WsMessage::Ping(_) | WsMessage::Pong(_) => continue,
                WsMessage::Close(_) => {
                    return Err(anyhow::anyhow!("WebSocket closed by Chrome"));
                }
                _ => continue,
            };
            let val: Value = serde_json::from_str(&text)?;
            if val["id"] == id {
                if let Some(err) = val.get("error") {
                    return Err(anyhow::anyhow!("CDP error: {}", err));
                }
                return Ok(val);
            }
            // It's an event or different response — keep waiting.
        }
    }

    // -----------------------------------------------------------------------
    // Session take/restore helpers
    //
    // We avoid holding a MutexGuard across await points by taking ownership
    // of the session, performing all async operations with it, then putting
    // it back into the global.
    // -----------------------------------------------------------------------

    fn take_session() -> anyhow::Result<ChromeSession> {
        SESSION.lock().take().ok_or_else(|| {
            anyhow::anyhow!("No active Chrome session. Run `/chrome connect` first.")

View on GitHub (pinned to b0637c97ec)