{"record":{"id":"59789ff9ff506b66","repo":"BigPizzaV3/CodexPlusPlus","slug":"browser-websocket-url-has-no-browser-id","errorCode":null,"errorMessage":"browser WebSocket URL has no Browser ID","messagePattern":"browser WebSocket URL has no Browser ID","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/cdp.rs","lineNumber":41,"sourceCode":"\n#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]\npub struct CdpBrowserIdentity {\n    #[serde(rename = \"Browser\")]\n    pub browser: String,\n    #[serde(rename = \"webSocketDebuggerUrl\")]\n    pub web_socket_debugger_url: String,\n}\n\nimpl CdpBrowserIdentity {\n    pub fn browser_id(&self) -> anyhow::Result<String> {\n        let url = reqwest::Url::parse(&self.web_socket_debugger_url)\n            .context(\"invalid browser WebSocket URL\")?;\n        let mut segments = url\n            .path_segments()\n            .ok_or_else(|| anyhow::anyhow!(\"browser WebSocket URL has no path\"))?;\n        match (segments.next(), segments.next(), segments.next()) {\n            (Some(\"devtools\"), Some(\"browser\"), Some(id)) if !id.is_empty() => Ok(id.to_string()),\n            _ => bail!(\"browser WebSocket URL has no Browser ID\"),\n        }\n    }\n}\n\n/// Returns whether the requested loopback port exposes a CDP target list.\npub(crate) fn endpoint_available(debug_port: u16) -> bool {\n    [\n        SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), debug_port),\n        SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), debug_port),\n    ]\n    .into_iter()\n    .any(|address| probe_endpoint(address, debug_port))\n}\n\nfn probe_endpoint(address: SocketAddr, debug_port: u16) -> bool {\n    let Ok(mut stream) = TcpStream::connect_timeout(&address, CDP_PROBE_TIMEOUT) else {\n        return false;\n    };","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/cdp.rs#L23-L59","documentation":"Thrown by `browser_id` when a Chromium DevTools (CDP) browser WebSocket URL does not contain a path segment shaped like `/devtools/browser/<id>`. The library only accepts DevTools browser endpoints whose final path segment is a non-empty Browser ID, so it can extract and return the browser identifier.","triggerScenarios":"Calling `browser_id` with a URL whose path is missing (e.g. `ws://127.0.0.1:9222`), malformed, or whose segments are not exactly `devtools`, `browser`, `<non-empty id>` — e.g. a page-level `/devtools/page/<id>` URL or an empty ID.","commonSituations":"Passing a page target's webSocketDebuggerUrl instead of the browser-level one, the browser returning a URL without an ID (older/patched Chromium builds, proxies stripping path segments), or a truncated/hand-edited endpoint string.","solutions":["Use the browser-level webSocketDebuggerUrl from the `/json/version` endpoint (it ends in /devtools/browser/<id>), not a page target URL","Verify the URL has a non-empty final path segment: print `url.path_segments()` before calling","Re-fetch the endpoint from a running browser rather than caching a stale URL string"],"exampleFix":"// before\nlet id = cdp::browser_id(\"ws://127.0.0.1:9222/devtools/page/ABC\")?;\n// after\nlet version: serde_json::Value = reqwest::get(\"http://127.0.0.1:9222/json/version\").await?.json().await?;\nlet ws_url = version[\"webSocketDebuggerUrl\"].as_str().unwrap();\nlet id = cdp::browser_id(ws_url)?;","handlingStrategy":"validation","validationCode":"fn looks_like_browser_ws_url(url: &str) -> bool {\n    reqwest::Url::parse(url).ok()\n        .and_then(|u| u.path_segments().map(|s| s.collect::<Vec<_>>()))\n        .map(|segs| segs.len() >= 3 && segs[0] == \"devtools\" && segs[1] == \"browser\" && !segs[2].is_empty())\n        .unwrap_or(false)\n}","typeGuard":"fn is_browser_ws_url(u: &reqwest::Url) -> bool {\n    u.scheme().starts_with(\"ws\")\n        && u.path_segments().map(|mut s| {\n            s.next() == Some(\"devtools\")\n                && s.next() == Some(\"browser\")\n                && s.next().map_or(false, |id| !id.is_empty())\n        }).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Always take the URL from /json/version's webSocketDebuggerUrl field","Don't reuse page-target webSocketDebuggerUrls for browser-level calls","Log the full URL when parsing fails to spot truncated endpoints"],"tags":["cdp","websocket-url","parsing"],"backgroundTag":"invalid-url-format","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}