xai-org/grok-build · error

WebSocket connection timed out after {} seconds

Error message

WebSocket connection timed out after {} seconds

What it means

connect_to_relay wraps the WebSocket handshake in tokio::time::timeout with CONNECT_TIMEOUT_SECS. If the handshake future is still pending when the timeout elapses, the elapsed branch bails with "WebSocket connection timed out after {N} seconds".

Source

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

                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"))
            }
        }) => {
            match result {
                Ok(Ok((ws, resp))) => {
                    if let Some(proto) = resp.headers().get("Sec-WebSocket-Protocol") {
                        info!(subprotocol = ?proto, "WS subprotocol negotiated");
                    }
                    Ok(ws)
                }
                Ok(Err(e)) => Err(e),
                Err(_) => anyhow::bail!("WebSocket connection timed out after {} seconds", CONNECT_TIMEOUT_SECS),
            }
        }
    }
}
/// Run a single WebSocket session, handling messages until disconnection.
pub(crate) async fn run_websocket_session<S>(
    ws: tokio_tungstenite::WebSocketStream<S>,
    to_agent_tx: &mpsc::UnboundedSender<String>,
    from_agent_rx: &mut mpsc::UnboundedReceiver<String>,
    cancel: &CancellationToken,
) -> anyhow::Result<SessionEndReason>
where
    S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static,
{
    run_websocket_session_with_liveness(
        ws,
        to_agent_tx,
        from_agent_rx,

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Verify network connectivity to the relay host/port (curl or ping the host).
  2. Check firewall/VPN rules — silent drops (no RST) are the usual cause of a full-timeout rather than a refused error.
  3. Confirm the relay URL/origin configuration points at the correct host.
  4. Retry with backoff; if behind a proxy, test whether the proxy is the bottleneck (also see proxy CONNECT errors).

Example fix

// before
let ws = connect_to_relay(&config, &cancel, proxy).await?;
// after
let ws = retry_with_backoff(3, || async {
    connect_to_relay(&config, &cancel, proxy).await
}).await?;
Defensive patterns

Strategy: retry

Try / catch

match connect_to_relay(&config, &cancel, proxy).await {
    Err(e) if e.to_string().contains("timed out after") => {
        // retry with backoff, then surface a clear connectivity error
        retry_with_backoff(3, || connect_to_relay(&config, &cancel, proxy)).await
            .context("relay unreachable: check network/firewall and relay host")
    }
    other => other,
}

Prevention

When it happens

Trigger: The WS handshake (optionally via proxy CONNECT + TLS + tokio-tungstenite handshake) does not complete within CONNECT_TIMEOUT_SECS — the timeout Err(_) arm fires.

Common situations: Relay host unreachable (packets silently dropped by firewall); DNS resolving but routing black-holed; very slow network or heavily loaded proxy; wrong relay origin/host configured.

Understand the failure class

Related errors


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