zed-industries/zed · error

Failed to connect to API: {} {}

Error message

Failed to connect to API: {} {}

What it means

Thrown by stream_completion in Zed's Copilot chat client when the POST to the Copilot chat-completions endpoint (completion_url, with Copilot vision/user-initiated headers) returns a non-2xx status. The message embeds the HTTP status code and the raw response body, which carries the real reason: 401 expired OAuth token, 403 missing Copilot entitlement, 429 rate limiting, or a GitHub 5xx. It fires before any SSE line parsing, so no partial stream content is produced.

Source

Thrown at crates/copilot_chat/src/copilot_chat.rs:1080

        &oauth_token,
        Some(is_user_initiated),
        Some(location),
    )
    .when(is_vision_request, |builder| {
        builder.header("Copilot-Vision-Request", is_vision_request.to_string())
    });

    let is_streaming = request.stream;

    let json = serde_json::to_string(&request)?;
    let request = request_builder.body(AsyncBody::from(json))?;
    let mut response = client.send(request).await?;

    if !response.status().is_success() {
        let mut body = Vec::new();
        response.body_mut().read_to_end(&mut body).await?;
        let body_str = std::str::from_utf8(&body)?;
        anyhow::bail!(
            "Failed to connect to API: {} {}",
            response.status(),
            body_str
        );
    }

    if is_streaming {
        let reader = BufReader::new(response.into_body());
        Ok(reader
            .lines()
            .filter_map(|line| async move {
                match line {
                    Ok(line) => {
                        let line = line.strip_prefix("data: ")?;
                        if line.starts_with("[DONE]") {
                            return None;
                        }

View on GitHub (pinned to f4178619ac)

Solutions

  1. If status is 401: sign out and back into Copilot so a fresh OAuth token is issued
  2. If 403 with entitlement text: verify an active Copilot subscription at github.com/settings/copilot
  3. If 429: back off and retry after the indicated interval; reduce request frequency
  4. Read the embedded body text - it names the exact upstream reason; act on that specific status
  5. If 5xx or proxy HTML: check githubstatus.com and bypass/inspect the proxy

Example fix

// before: fire-and-forget call surfaces the raw bail
let stream = stream_completion(client, token, url, request, true, location).await?;

// after: retry transient statuses, fail fast on auth/entitlement
let mut attempt = 0;
loop {
    attempt += 1;
    match stream_completion(client.clone(), token.clone(), url.clone(), request.clone(), true, location).await {
        Ok(stream) => break stream,
        Err(err) if attempt < 3 && err.to_string().contains("429") => {
            smol::Timer::after(Duration::from_secs(2u64.pow(attempt))).await;
        }
        Err(err) if err.to_string().contains("401") => return Err(err.context("re-authenticate Copilot")),
        Err(err) => return Err(err),
    }
}
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: refresh the token before sending if it is near expiry
if oauth_token.expires_in_remaining() < Duration::from_secs(30) {
    oauth_token = refresh_copilot_token(client).await?;
}

Try / catch

match stream_completion(/* .. */).await {
    Ok(stream) => { /* handle stream */ }
    Err(err) => {
        let msg = err.to_string();
        if msg.contains("401") {
            // trigger re-auth flow
        } else if msg.contains("429") || msg.contains("50") {
            // schedule backoff retry
        } else {
            return Err(err);
        }
    }
}

Prevention

When it happens

Trigger: Any Copilot chat completion request (request.stream either true or false) sent with: a stale/revoked GitHub Copilot OAuth token (401), a user with no active Copilot subscription (403, body often contains 'copilot_not_enabled'), tripping secondary rate limits (429), oversized prompts rejected (413), or GitHub API incidents (5xx). Vision requests additionally send 'Copilot-Vision-Request: true' and can be rejected when vision is not entitled.

Common situations: Token revoked by changing GitHub password or re-authorizing elsewhere; free-tier or lapsed Copilot subscriptions; aggressive retry loops hitting rate limits; corporate proxies returning HTML error pages as the body; requests to a stale cached completion_url.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/3a07d51521f3012a. Report an issue: GitHub.