BigPizzaV3/CodexPlusPlus · error · anyhow::Error

browser WebSocket URL has no path

Error message

browser WebSocket URL has no path

What it means

CdpBrowserIdentity::browser_id (crates/codex-plus-core/src/cdp.rs:38) derives the browser ID from the path of the /json/version webSocketDebuggerUrl and expects the shape /devtools/browser/<id>. path_segments() returns None only for URLs that cannot be a base (opaque-path URLs, e.g. 'ws:devtools' or a bare non-hierarchical string that still parses), so this error indicates a malformed, non-hierarchical debugger URL rather than a merely empty path.

Source

Thrown at crates/codex-plus-core/src/cdp.rs:38

    #[serde(default, rename = "webSocketDebuggerUrl")]
    pub web_socket_debugger_url: Option<String>,
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct CdpBrowserIdentity {
    #[serde(rename = "Browser")]
    pub browser: String,
    #[serde(rename = "webSocketDebuggerUrl")]
    pub web_socket_debugger_url: String,
}

impl CdpBrowserIdentity {
    pub fn browser_id(&self) -> anyhow::Result<String> {
        let url = reqwest::Url::parse(&self.web_socket_debugger_url)
            .context("invalid browser WebSocket URL")?;
        let mut segments = url
            .path_segments()
            .ok_or_else(|| anyhow::anyhow!("browser WebSocket URL has no path"))?;
        match (segments.next(), segments.next(), segments.next()) {
            (Some("devtools"), Some("browser"), Some(id)) if !id.is_empty() => Ok(id.to_string()),
            _ => bail!("browser WebSocket URL has no Browser ID"),
        }
    }
}

/// Returns whether the requested loopback port exposes a CDP target list.
pub(crate) fn endpoint_available(debug_port: u16) -> bool {
    [
        SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), debug_port),
        SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), debug_port),
    ]
    .into_iter()
    .any(|address| probe_endpoint(address, debug_port))
}

fn probe_endpoint(address: SocketAddr, debug_port: u16) -> bool {

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. Use the webSocketDebuggerUrl exactly as returned by the real endpoint's /json/version — do not rewrite it
  2. Validate the URL shape (scheme ws/wss + host + /devtools/browser/<id> path) before constructing CdpBrowserIdentity or calling browser_id()
  3. If you control the server, return a full hierarchical URL: ws://127.0.0.1:9222/devtools/browser/<uuid>

Example fix

// before: opaque URL — Url::parse succeeds, path_segments() is None
let id = identity.browser_id()?; // web_socket_debugger_url = "ws:browser"

// after: full hierarchical URL from /json/version
// web_socket_debugger_url = "ws://127.0.0.1:9222/devtools/browser/<uuid>"
let id = identity.browser_id()?; // Ok("<uuid>")
Defensive patterns

Strategy: validation

Validate before calling

// Require the /devtools/browser/<id> shape before deriving a browser id
let u = reqwest::Url::parse(&identity.web_socket_debugger_url)?;
ensure!(u.path_segments().is_some(), "URL has no hierarchical path");
ensure!(u.path().starts_with("/devtools/browser/"), "unexpected CDP path shape");
let id = identity.browser_id()?;

Type guard

fn is_hierarchical_ws_url(url: &str) -> bool {
    reqwest::Url::parse(url)
        .ok()
        .and_then(|u| u.path_segments().map(|mut s| s.next().is_some()))
        .unwrap_or(false)
}

Try / catch

match identity.browser_id() {
    Ok(id) => Ok(id),
    Err(e) if e.to_string().contains("no path") || e.to_string().contains("Browser ID") => {
        // discard the malformed identity and re-fetch /json/version from the endpoint
        refetch_browser_identity(port).await?.browser_id()
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Calling browser_id() on a CdpBrowserIdentity whose web_socket_debugger_url parses via Url::parse but has no hierarchical path — hand-built strings like "ws:browser" or data produced by a non-Chromium endpoint that mimics /json/version with an opaque ws URL.

Common situations: A mock/stub CDP endpoint used in tests returning a shorthand URL; a browser fork or other DevTools-protocol server whose version payload differs from Chrome's; string munging (schema prefix stripping) that turns ws://host/devtools/... into an opaque form.

Related errors


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