BigPizzaV3/CodexPlusPlus · error · anyhow::Error

selected CDP target has no websocket URL

Error message

selected CDP target has no websocket URL

What it means

bridge_health_ok (crates/codex-plus-core/src/launcher.rs:2473) lists DevTools targets, picks the injectable Codex page via cdp::pick_injectable_codex_page_target, and requires the chosen target to carry a webSocketDebuggerUrl before evaluating the health-check script over its WebSocket. The picker's is_injectable_page_target predicate already demands a non-empty webSocketDebuggerUrl, so this guard fires only when that invariant is broken (target list mutated between filter and use, or a forked picker that dropped the predicate) — normally the health check fails earlier with 'No injectable Codex page target found'.

Source

Thrown at crates/codex-plus-core/src/launcher.rs:2479

}

async fn run_bridge_reinjector(
    bridge_reinjector: Option<BridgeReinjector>,
    default_reinjector: BridgeReinjector,
) -> anyhow::Result<()> {
    match bridge_reinjector {
        Some(reinject) => reinject().await,
        None => default_reinjector().await,
    }
}

async fn bridge_health_ok(debug_port: u16) -> anyhow::Result<bool> {
    let targets = crate::cdp::list_targets(debug_port).await?;
    let target = crate::cdp::pick_injectable_codex_page_target(&targets)?;
    let websocket_url = target
        .web_socket_debugger_url
        .as_deref()
        .ok_or_else(|| anyhow::anyhow!("selected CDP target has no websocket URL"))?;
    let result = crate::bridge::evaluate_script_with_await_promise(
        websocket_url,
        crate::bridge::bridge_health_check_script(),
        true,
    )
    .await?;
    Ok(runtime_evaluate_result_is_true(&result))
}

fn runtime_evaluate_result_is_true(result: &Value) -> bool {
    result
        .get("result")
        .and_then(|result| result.get("result"))
        .and_then(|result| result.get("value"))
        .and_then(Value::as_bool)
        .unwrap_or(false)
}

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. Retry the health check after a short delay — transient target churn resolves once the page settles
  2. Verify the target list manually: GET http://127.0.0.1:<debug_port>/json/list and confirm an entry with type 'page', url app://--/index.html, and a non-empty webSocketDebuggerUrl
  3. If you forked pick_injectable_codex_page_target, restore the is_injectable_page_target filter so only WS-bearing pages are selectable
  4. Ensure you are pointing at Codex's debug port (select_packaged_codex_debug_port), not another Chromium instance

Example fix

// before: single-shot health probe races target teardown
let ok = bridge_health_ok(debug_port).await?; // bail: no websocket URL

// after: tolerate transient churn
let ok = match bridge_health_ok(debug_port).await {
    Ok(v) => v,
    Err(e) if e.to_string().contains("no websocket URL") => {
        tokio::time::sleep(Duration::from_millis(500)).await;
        bridge_health_ok(debug_port).await.unwrap_or(false)
    }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: retry

Validate before calling

async fn health_target_has_ws(debug_port: u16) -> bool {
    match crate::cdp::list_targets(debug_port).await {
        Ok(targets) => crate::cdp::pick_injectable_codex_page_target(&targets)
            .map(|t| t.web_socket_debugger_url.as_deref().is_some_and(|u| !u.is_empty()))
            .unwrap_or(false),
        Err(_) => false,
    }
}

Type guard

fn target_has_websocket(t: &crate::cdp::CdpTarget) -> bool {
    t.web_socket_debugger_url.as_deref().is_some_and(|u| !u.is_empty())
}

Try / catch

// Treat as transient churn: one retry, then degrade to 'unhealthy'
let healthy = match bridge_health_ok(debug_port).await {
    Ok(v) => v,
    Err(e) if e.to_string().contains("no websocket URL") => {
        tokio::time::sleep(Duration::from_millis(500)).await;
        bridge_health_ok(debug_port).await.unwrap_or(false)
    }
    Err(e) => { tracing::warn!(%e, "bridge health probe failed"); false }
};

Prevention

When it happens

Trigger: Querying bridge health on a debug port where the selected page target lacks webSocketDebuggerUrl: Chrome-family browsers omit that field for targets being destroyed or for browser-level targets; races where /json/list returns a page that closes before the URL is read; fork code relaxing pick_injectable_codex_page_target.

Common situations: Codex app mid-shutdown or mid-navigation when health is polled; calling bridge_health_ok against a plain Chromium debug port (no app:// page with WS URL); stale target caches from a wrapped list_targets.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16). Data as JSON: /api/errors/7166be0b5220d20c. Report an issue: GitHub.