BigPizzaV3/CodexPlusPlus · error

browser WebSocket URL has no path

Error message

browser WebSocket URL has no path

What it means

`CdpBrowserIdentity::browser_id` parses the browser's `webSocketDebuggerUrl` and extracts the Browser ID from the path segment after `/devtools/browser/`. If the URL parses but has no path segments at all (`url.path_segments()` returns None — only possible for URLs without a path), this error is thrown. A URL with a path but wrong shape yields the sibling 'no Browser ID' error instead.

Solutions

  1. Use the exact `webSocketDebuggerUrl` from http://127.0.0.1:<port>/json/version
  2. Re-fetch /json/version; the browser may have restarted with a new Browser ID
  3. Validate the URL matches /devtools/browser/<id> before constructing CdpBrowserIdentity
  4. Check any code that rewrites the host/port portion of the URL preserves the path

Example fix

// before
let identity = CdpBrowserIdentity { web_socket_debugger_url: "ws://127.0.0.1:9222".into() };
// after
let ws_url = fetch_json_version(port)?.webSocketDebuggerUrl; // ws://127.0.0.1:9222/devtools/browser/<id>
let identity = CdpBrowserIdentity { web_socket_debugger_url: ws_url };
Defensive patterns

Strategy: validation

Validate before calling

let u = reqwest::Url::parse(&ws_url)?;
let segs: Vec<_> = u.path_segments().map(|s| s.collect()).unwrap_or_default();
if !(segs.len() == 3 && segs[0] == "devtools" && segs[1] == "browser" && !segs[2].is_empty()) {
    return Err(anyhow!("not a valid browser WebSocket URL: {ws_url}"));
}

Try / catch

match identity.browser_id() {
    Err(e) if e.to_string().contains("no path") || e.to_string().contains("no Browser ID") => {
        let fresh = fetch_web_socket_debugger_url(port)?; // re-query /json/version
        retry_with(fresh)
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling `browser_id()` on an identity whose `web_socket_debugger_url` is like `ws://127.0.0.1:9222` with no path, or an empty-path URL from a malformed /json/version response.

Common situations: Chrome's /json/version returned a truncated webSocketDebuggerUrl, a proxy stripped the path, or the field was set from user input rather than the real DevTools response.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/022373cb4ff53a18. Report an issue: GitHub.

Appendix: 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 b1ed92e5e4)