BigPizzaV3/CodexPlusPlus · error · anyhow::Error
CDP WebSocket URL has no host
Error message
CDP WebSocket URL has no host
What it means
validate_cdp_websocket_url (crates/codex-plus-core/src/cdp.rs:218) requires the parsed URL to have a host component; host_str() returns None when the authority is empty or the URL is not hierarchical (e.g. 'ws:///devtools/browser/id'). This is the earliest structural check after the ws/wss scheme check, so it fires before the loopback/port validation can run.
Source
Thrown at crates/codex-plus-core/src/cdp.rs:218
.context("failed to deserialize CDP targets")?;
for target in &targets {
if let Some(websocket_url) = target.web_socket_debugger_url.as_deref() {
validate_cdp_websocket_url(websocket_url, debug_port).with_context(|| {
format!("unsafe CDP target WebSocket URL for target {}", target.id)
})?;
}
}
Ok(targets)
}
pub fn validate_cdp_websocket_url(url: &str, expected_port: u16) -> anyhow::Result<()> {
let parsed = reqwest::Url::parse(url).context("invalid CDP WebSocket URL")?;
if !matches!(parsed.scheme(), "ws" | "wss") {
bail!("CDP WebSocket URL must use ws or wss");
}
let host = parsed
.host_str()
.ok_or_else(|| anyhow::anyhow!("CDP WebSocket URL has no host"))?;
let address = host
.trim_start_matches('[')
.trim_end_matches(']')
.parse::<IpAddr>()
.with_context(|| "CDP WebSocket host must be a loopback IP address")?;
if !address.is_loopback() {
bail!("CDP WebSocket host must be loopback");
}
let port = parsed
.port()
.ok_or_else(|| anyhow::anyhow!("CDP WebSocket URL must include an explicit port"))?;
if port != expected_port {
bail!("CDP WebSocket port {port} does not match debug port {expected_port}");
}
Ok(())
}
pub fn pick_page_target(targets: &[CdpTarget]) -> anyhow::Result<CdpTarget> {View on GitHub (pinned to 1f431ae49b)
Solutions
- Fill in the host: ws://127.0.0.1:9222/devtools/browser/<id> (must be a loopback IP to pass the next check)
- Trace where the URL string is assembled and guard the empty-host case before calling the validator
- Prefer taking the URL from /json/version of the running browser instead of assembling it yourself
Example fix
# before: empty authority ws:///devtools/browser/4f8a... # after: explicit loopback host (and port) ws://127.0.0.1:9222/devtools/browser/4f8a...
Defensive patterns
Strategy: validation
Validate before calling
// Check host presence (and loopback-ness) before validation/connect
let u = reqwest::Url::parse(ws_url)?;
let host = u.host_str().context("CDP WebSocket URL has no host")?;
ensure!(host.trim_start_matches('[').trim_end_matches(']').parse::<std::net::IpAddr>().is_ok_and(|a| a.is_loopback()), "host must be loopback IP");
validate_cdp_websocket_url(ws_url, expected_port)?; Type guard
fn ws_url_has_host(url: &str) -> bool {
reqwest::Url::parse(url).ok().and_then(|u| u.host_str().map(|_| true)).unwrap_or(false)
} Try / catch
match validate_cdp_websocket_url(ws_url, port) {
Ok(()) => Ok(()),
Err(e) if e.to_string().contains("no host") => {
// template bug: rebuild URL from known loopback host + port and retry once
let repaired = format!("ws://127.0.0.1:{port}/devtools/browser/{id}");
validate_cdp_websocket_url(&repaired, port)
}
Err(e) => Err(e),
} Prevention
- Assemble ws URLs only from validated non-empty host variables
- Assert !host.is_empty() in URL-building helpers
- Prefer endpoint-provided URLs over string templating
When it happens
Trigger: Validating a URL string like ws:///devtools/browser/<id> (empty host) or an opaque 'ws:...' string; passing a URL whose host was lost during templating/formatting (e.g. format!("ws://{}/devtools/...", host) with an empty host variable).
Common situations: Config templating that concatenates an unset/empty host variable; copy-paste that drops '127.0.0.1:9222' from the URL; environment-driven host configuration that is empty in CI.
Related errors
- CDP WebSocket URL must include an explicit port
- CDP WebSocket URL must include an explicit port
- Page.captureScreenshot returned no image data
- browser WebSocket URL has no path
- selected CDP target has no websocket URL
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16).
Data as JSON: /api/errors/254afc97b6c8ab95.
Report an issue: GitHub.