{"record":{"id":"254afc97b6c8ab95","repo":"BigPizzaV3/CodexPlusPlus","slug":"cdp-websocket-url-has-no-host","errorCode":null,"errorMessage":"CDP WebSocket URL has no host","messagePattern":"CDP WebSocket URL has no host","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/cdp.rs","lineNumber":218,"sourceCode":"        .context(\"failed to deserialize CDP targets\")?;\n    for target in &targets {\n        if let Some(websocket_url) = target.web_socket_debugger_url.as_deref() {\n            validate_cdp_websocket_url(websocket_url, debug_port).with_context(|| {\n                format!(\"unsafe CDP target WebSocket URL for target {}\", target.id)\n            })?;\n        }\n    }\n    Ok(targets)\n}\n\npub fn validate_cdp_websocket_url(url: &str, expected_port: u16) -> anyhow::Result<()> {\n    let parsed = reqwest::Url::parse(url).context(\"invalid CDP WebSocket URL\")?;\n    if !matches!(parsed.scheme(), \"ws\" | \"wss\") {\n        bail!(\"CDP WebSocket URL must use ws or wss\");\n    }\n    let host = parsed\n        .host_str()\n        .ok_or_else(|| anyhow::anyhow!(\"CDP WebSocket URL has no host\"))?;\n    let address = host\n        .trim_start_matches('[')\n        .trim_end_matches(']')\n        .parse::<IpAddr>()\n        .with_context(|| \"CDP WebSocket host must be a loopback IP address\")?;\n    if !address.is_loopback() {\n        bail!(\"CDP WebSocket host must be loopback\");\n    }\n    let port = parsed\n        .port()\n        .ok_or_else(|| anyhow::anyhow!(\"CDP WebSocket URL must include an explicit port\"))?;\n    if port != expected_port {\n        bail!(\"CDP WebSocket port {port} does not match debug port {expected_port}\");\n    }\n    Ok(())\n}\n\npub fn pick_page_target(targets: &[CdpTarget]) -> anyhow::Result<CdpTarget> {","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/cdp.rs#L200-L236","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"# before: empty authority\nws:///devtools/browser/4f8a...\n\n# after: explicit loopback host (and port)\nws://127.0.0.1:9222/devtools/browser/4f8a...","handlingStrategy":"validation","validationCode":"// Check host presence (and loopback-ness) before validation/connect\nlet u = reqwest::Url::parse(ws_url)?;\nlet host = u.host_str().context(\"CDP WebSocket URL has no host\")?;\nensure!(host.trim_start_matches('[').trim_end_matches(']').parse::<std::net::IpAddr>().is_ok_and(|a| a.is_loopback()), \"host must be loopback IP\");\nvalidate_cdp_websocket_url(ws_url, expected_port)?;","typeGuard":"fn ws_url_has_host(url: &str) -> bool {\n    reqwest::Url::parse(url).ok().and_then(|u| u.host_str().map(|_| true)).unwrap_or(false)\n}","tryCatchPattern":"match validate_cdp_websocket_url(ws_url, port) {\n    Ok(()) => Ok(()),\n    Err(e) if e.to_string().contains(\"no host\") => {\n        // template bug: rebuild URL from known loopback host + port and retry once\n        let repaired = format!(\"ws://127.0.0.1:{port}/devtools/browser/{id}\");\n        validate_cdp_websocket_url(&repaired, port)\n    }\n    Err(e) => Err(e),\n}","preventionTips":["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"],"tags":["cdp","url-parsing","websocket","host"],"backgroundTag":"malformed-websocket-url","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}