BigPizzaV3/CodexPlusPlus · error · anyhow::Error

CDP WebSocket URL must include an explicit port

Error message

CDP WebSocket URL must include an explicit port

What it means

connect_cdp_websocket (crates/codex-plus-core/src/bridge.rs:291) parses the ws URL and requires Url::port() to be Some before validating it against the debug port. For ws/wss schemes reqwest::Url has no well-known default port (only http/https/ftp do), so a URL like ws://127.0.0.1/devtools/browser/<id> without ':9222' yields port()==None and this error fires. It is a strict-input guard: the code refuses to guess a port.

Source

Thrown at crates/codex-plus-core/src/bridge.rs:291

}

pub fn reject_bridge_expression(request_id: &str, message: &str) -> anyhow::Result<String> {
    Ok(format!(
        "window.__codexSessionDeleteReject({}, {})",
        serde_json::to_string(request_id)?,
        serde_json::to_string(message)?,
    ))
}

async fn connect_cdp_websocket(
    websocket_url: &str,
) -> anyhow::Result<
    tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>,
> {
    let parsed = reqwest::Url::parse(websocket_url).context("invalid CDP WebSocket URL")?;
    let port = parsed
        .port()
        .ok_or_else(|| anyhow::anyhow!("CDP WebSocket URL must include an explicit port"))?;
    crate::cdp::validate_cdp_websocket_url(websocket_url, port)?;
    let (socket, _) = tokio::time::timeout(CDP_CONNECT_TIMEOUT, connect_async(websocket_url))
        .await
        .with_context(|| {
            format!(
                "timed out connecting CDP websocket after {}s",
                CDP_CONNECT_TIMEOUT.as_secs()
            )
        })?
        .context("failed to connect CDP websocket")?;

    Ok(socket)
}

struct CdpSession<S> {
    socket: S,
    responses: HashMap<u64, Value>,
    binding_calls: VecDeque<Value>,

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. Include the explicit port in the URL: ws://127.0.0.1:9222/devtools/browser/<id>
  2. Source the URL from the browser itself: GET http://127.0.0.1:<port>/json/version and use its webSocketDebuggerUrl verbatim (it always includes the port)
  3. If accepting user input, parse with Url and reject/repair port-less ws URLs before they reach the connector

Example fix

# before: no port — Url::port() returns None for ws scheme
ws://127.0.0.1/devtools/browser/4f8a... 

# after: explicit port matching the remote debugging port
ws://127.0.0.1:9222/devtools/browser/4f8a...
Defensive patterns

Strategy: validation

Validate before calling

// Validate shape before connecting
let u = reqwest::Url::parse(ws_url).context("invalid CDP WebSocket URL")?;
let port = u.port().context("CDP WebSocket URL must include an explicit port")?;
validate_cdp_websocket_url(ws_url, port)?; // also enforces ws/wss + loopback + port match

Type guard

fn cdp_url_has_explicit_port(url: &str) -> bool {
    reqwest::Url::parse(url).ok().and_then(|u| u.port()).is_some()
}

Try / catch

match connect_cdp_websocket(ws_url).await {
    Ok(socket) => Ok(socket),
    Err(e) if e.to_string().contains("explicit port") => {
        // repair by re-fetching the canonical URL from the browser
        let fresh = fetch_websocket_debugger_url(port).await?;
        connect_cdp_websocket(&fresh).await
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Passing a CDP WebSocket URL assembled by hand or trimmed of its port, e.g. ws://localhost/devtools/browser/abc, into any bridge helper that connects (screenshot, command send); URLs taken from the browser's /json/version endpoint always carry the explicit port and never trigger this.

Common situations: User config stores a sanitized URL without the port; code copies webSocketDebuggerUrl but strips ':9222' via a rewriting rule; a different default debug port (not 9222) makes hand-written URLs omit it; proxy/URL normalization middleware drops 'unnecessary' ports.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16). Data as JSON: /api/errors/8d0d4d5b509bc459. Report an issue: GitHub.