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
- Fix the relay URL so it is an absolute URI with a host, e.g. wss://relay.example.com/ws
- Validate the URL with url::Url::parse and check host_str().is_some() before running the loop
- 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
- Store relay endpoints as absolute ws:// or wss:// URLs
- Parse and validate relay URLs once at startup
- Build URLs with url::Url instead of string concatenation
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
- Invalid GCS URL scheme: expected 'gs', got '{}'
- A URL is required for {label} servers. Usage: grok mcp add -
- Invalid URL '{url}'. Server URLs must start with http:// or
- Proxy CONNECT failed: {}
- Proxy sent {} unexpected byte(s) after CONNECT response head
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/d769385d2a244047.
Report an issue: GitHub.