n0-computer/iroh · error · ConnectError

UnexpectedUpgradeStatus

UnexpectedUpgradeStatus

Error message

Unexpected status during upgrade: {code}

What it means

ConnectError::UnexpectedUpgradeStatus from iroh-relay's websocket-based client connect. After issuing the HTTP upgrade request over the established stream, the client requires hyper's 101 SWITCHING_PROTOCOLS status; any other status (e.g. 4xx/5xx from an intermediary) fails the websocket upgrade and this error carries the actual status code.

Solutions

  1. Inspect the status code in the error: 401/403 means auth is missing/invalid; 404 means wrong URL/path; 502/503 means a proxy or relay is down.
  2. Verify the relay URL, scheme (wss vs ws), and that the server supports the websocket transport.
  3. Bypass or correctly configure intermediate proxies to pass Upgrade/Connection headers.
  4. Update the iroh-relay client/server so protocol versions match.

Example fix

match relay_client.connect().await {
    Ok(conn) => /* use conn */,
    Err(ConnectError::UnexpectedUpgradeStatus { code }) if code == 401 => {
        // refresh auth token then retry
    }
    Err(e) => return Err(e.into()),
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: confirm the relay URL answers an HTTP request before upgrading
let probe = reqwest::get(relay_url.join("ping")?).await?;
ensure!(probe.status().is_success(), "relay unreachable: {}", probe.status());

Try / catch

match client.connect().await {
    Err(e) if format!("{e}").contains("UnexpectedUpgradeStatus") => {
        // log status, check proxy/auth, retry with backoff
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling connect() on the relay websocket client when response.status() != 101 — e.g. the relay or a proxy replies with 400/401/403/404/500, or an HTTP/1.0 proxy mangles the Upgrade request.

Common situations: Corporate proxies or load balancers stripping WebSocket Upgrade headers; relays requiring auth returning 401/403; wrong relay URL or TLS termination returning 404/502; relay running an incompatible protocol version.

Related errors


AI-assisted analysis of n0-computer/iroh@2b4de030ce (2026-09-08). Data as JSON: /api/errors/9242dc8ee8d2223e. Report an issue: GitHub.

Appendix: source

Thrown at iroh-relay/src/client.rs:338

        if let Some(token) = self.auth_token.as_ref() {
            let value = HeaderValue::from_str(&format!("Bearer {token}"))
                .map_err(|_| e!(ConnectError::InvalidAuthToken))?;
            builder = builder
                .add_header(AUTHORIZATION, value)
                .expect("valid header name");
        }

        if let Some(client_auth) = KeyMaterialClientAuth::new(&self.secret_key, &stream) {
            debug!("Using TLS key export for relay client authentication");
            builder = builder
                .add_header(CLIENT_AUTH_HEADER, client_auth.into_header_value())
                .expect(
                    "impossible: CLIENT_AUTH_HEADER isn't a disallowed header value for websockets",
                );
        }
        let (conn, response) = builder.connect_on(stream).await.anyerr()?;

        n0_error::ensure!(
            response.status() == hyper::StatusCode::SWITCHING_PROTOCOLS,
            ConnectError::UnexpectedUpgradeStatus {
                code: response.status()
            }
        );

        let protocol_version_str = response
            .headers()
            .get(SEC_WEBSOCKET_PROTOCOL)
            .and_then(|s| s.to_str().ok());
        let protocol_version = protocol_version_str
            .and_then(ProtocolVersion::match_from_str)
            .ok_or_else(|| {
                e!(ConnectError::BadVersionHeader {
                    server_version: protocol_version_str.map(ToOwned::to_owned)
                })
            })?;

View on GitHub (pinned to 2b4de030ce)