BigPizzaV3/CodexPlusPlus · error · anyhow::Error
browser WebSocket URL has no Browser ID
Error message
browser WebSocket URL has no Browser ID
What it means
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.
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
Example fix
// before
let id = cdp::browser_id("ws://127.0.0.1:9222/devtools/page/ABC")?;
// after
let version: serde_json::Value = reqwest::get("http://127.0.0.1:9222/json/version").await?.json().await?;
let ws_url = version["webSocketDebuggerUrl"].as_str().unwrap();
let id = cdp::browser_id(ws_url)?; Defensive patterns
Strategy: validation
Validate before calling
fn looks_like_browser_ws_url(url: &str) -> bool {
reqwest::Url::parse(url).ok()
.and_then(|u| u.path_segments().map(|s| s.collect::<Vec<_>>()))
.map(|segs| segs.len() >= 3 && segs[0] == "devtools" && segs[1] == "browser" && !segs[2].is_empty())
.unwrap_or(false)
} Type guard
fn is_browser_ws_url(u: &reqwest::Url) -> bool {
u.scheme().starts_with("ws")
&& u.path_segments().map(|mut s| {
s.next() == Some("devtools")
&& s.next() == Some("browser")
&& s.next().map_or(false, |id| !id.is_empty())
}).unwrap_or(false)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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
- CDP WebSocket port does not match debug port
- CDP WebSocket URL must use ws or wss
- browser WebSocket URL has no path
- CDP command id failed
- CDP websocket closed before response for
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/59789ff9ff506b66.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/cdp.rs:41
#[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 {
let Ok(mut stream) = TcpStream::connect_timeout(&address, CDP_PROBE_TIMEOUT) else {
return false;
};View on GitHub (pinned to b1ed92e5e4)