xai-org/grok-build · error

WebSocket URL has no host

Error message

WebSocket URL has no host

What it means

connect_to_relay builds a WebSocket connection; on the proxy path it needs the target host from the request URI to issue an HTTP CONNECT. If the WS URI has no host component (e.g. a relative or scheme-only URL) this error is thrown.

Source

Thrown at crates/codegen/xai-grok-shell/src/agent/relay.rs:421

/// CONNECT tunnel.  Otherwise, a direct connection is used.
async fn connect_to_relay(
    config: &RelayConfig,
    proxy_url: Option<&str>,
    cancel: &CancellationToken,
) -> anyhow::Result<
    tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>,
> {
    let req = build_relay_request(config)?;
    let connect_timeout = Duration::from_secs(CONNECT_TIMEOUT_SECS);
    tokio::select! {
        _ = cancel.cancelled() => {
            anyhow::bail!("Connection cancelled");
        }
        result = tokio::time::timeout(connect_timeout, async {
            if let Some(proxy_url) = proxy_url {
                // Proxy path: open TCP to proxy, send CONNECT, then WS handshake.
                let target_host = req.uri().host()
                    .ok_or_else(|| anyhow::anyhow!("WebSocket URL has no host"))?;
                let target_port = req.uri().port_u16().unwrap_or(443);
                let tunneled_stream = proxy::connect_via_proxy(
                    proxy_url,
                    target_host,
                    target_port,
                ).await?;
                // Perform the WebSocket handshake over the tunneled stream.
                let (ws, resp) = tokio_tungstenite::client_async(req, tunneled_stream)
                    .await
                    .map_err(|e| anyhow::Error::from(e).context("WebSocket handshake via proxy failed"))?;
                Ok((ws, resp))
            } else {
                // The default connector never sees the shared trust config.
                let connector =
                    tokio_tungstenite::Connector::Rustls(xai_grok_extra_ca::rustls_client_config());
                connect_async_tls_with_config(req, None, false, Some(connector))
                    .await
                    .map_err(|e| anyhow::Error::from(e).context("WebSocket connection failed"))

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Fix the relay URL so it is an absolute URI with a host, e.g. wss://relay.example.com/ws
  2. Validate the URL with url::Url::parse and check host_str().is_some() before running the loop
  3. If built dynamically, use url::Url::builder instead of string concatenation

Example fix

// before
let relay = "wss:/relay.example.com";
// after
let relay = "wss://relay.example.com/ws";
Defensive patterns

Strategy: validation

Validate before calling

fn has_host(u: &str) -> bool {
    url::Url::parse(u).ok()
        .and_then(|u| u.host_str().map(|h| !h.is_empty()))
        .unwrap_or(false)
}
// assert!(has_host(&relay_url));

Try / catch

match result {
    Err(e) if e.to_string().contains("WebSocket URL has no host") => {
        eprintln!("relay URL malformed, must be absolute ws:// or wss:// with host");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling run_relay_loop with a relay WS URL like 'wss:/relay' (single slash), 'ws://' with empty authority, or a malformed/empty relay endpoint configured.

Common situations: Config typo dropping a slash in ws:// or wss:// URL, env var with trailing whitespace stripped of host, or relay URL constructed by string concatenation losing the authority.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/d769385d2a244047. Report an issue: GitHub.