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
- If status is 401: sign out and back into Copilot so a fresh OAuth token is issued
- If 403 with entitlement text: verify an active Copilot subscription at github.com/settings/copilot
- If 429: back off and retry after the indicated interval; reduce request frequency
- Read the embedded body text - it names the exact upstream reason; act on that specific status
- 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
- Refresh the Copilot OAuth token proactively before it expires rather than after a 401
- Wrap completions in bounded exponential-backoff retries for 429/5xx only
- Log and persist the status/body pair - it distinguishes auth vs entitlement vs capacity instantly
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
- Failed to connect to API: {} {}
- The Copilot sign-in code expired. Please try again.
- could not locate run '{run_id}' in the local run index ({run
- unsupported Copilot language server architecture: {architect
- Copilot sign-in was cancelled.
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/3a07d51521f3012a.
Report an issue: GitHub.