zed-industries/zed · error
token request failed with status {status}: {error_body}
Error message
token request failed with status {status}: {error_body} What it means
post_token_request() (used by exchange_code and refresh_tokens) got a non-2xx from the token endpoint. Before bailing, it tries to parse the body as a structured OAuth error per RFC 6749 section 5.2 (invalid_grant, invalid_client, ...); this generic message appears only when the body did NOT parse as an OAuth error JSON — i.e. the server failed in a non-standard way (HTML error page, empty body, gateway error, plain text). The message carries the HTTP status and the raw body text.
Source
Thrown at crates/context_server/src/oauth.rs:1036
let request = Request::builder()
.method(http_client::http::Method::POST)
.uri(token_endpoint.as_str())
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Accept", "application/json")
.body(AsyncBody::from(body.into_bytes()))?;
let mut response = http_client.send(request).await?;
if !response.status().is_success() {
let mut error_body = String::new();
response.body_mut().read_to_string(&mut error_body).await?;
let status = response.status();
// Try to parse as an OAuth error response (RFC 6749 Section 5.2).
if let Ok(token_error) = serde_json::from_str::<OAuthTokenError>(&error_body) {
return Err(token_error.into());
}
bail!("token request failed with status {status}: {error_body}");
}
let mut response_body = String::new();
response
.body_mut()
.read_to_string(&mut response_body)
.await?;
let token_response: TokenResponse =
serde_json::from_str(&response_body).context("failed to parse token response")?;
Ok(token_response.into_tokens())
}
// -- Loopback HTTP callback server -------------------------------------------
/// An OAuth authorization callback received via the loopback HTTP server.
pub struct OAuthCallback {View on GitHub (pinned to f4178619ac)
Solutions
- Inspect the {status} in the message: 5xx usually means a backend/proxy failure — check auth server logs and proxy health before touching client config
- For 400/401, compare the raw body against RFC 6749 section 5.2 — if the server can be configured to emit standard error codes, do so and the structured path will give clearer errors
- Redo the flow from the start if the authorization code may have expired or been consumed (codes are single-use; a double redirect or reload burns them)
- Verify client_id/secret and redirect_uri exactly match what was used at the authorization request
Example fix
# before (non-standard error body from server)
HTTP/1.1 400 Bad Request
invalid code
# after (standard RFC 6749 error)
HTTP/1.1 400 Bad Request
Content-Type: application/json
{"error":"invalid_grant","error_description":"authorization code expired"} Defensive patterns
Strategy: try-catch
Try / catch
match exchange_code(&client, &metadata, &code, &client_id, &redirect_uri, &verifier, &resource, secret).await {
Ok(tokens) => Ok(tokens),
Err(err) if err.to_string().contains("token request failed") => {
let msg = err.to_string();
if msg.contains("5") && msg.contains("Bad Gateway|Gateway|50") {
// infrastructure error: safe to retry the exchange once with the same code
retry_once(small_backoff).await
} else {
// 4xx: the code/credentials are likely consumed or invalid — restart the flow, do NOT retry
restart_authorization_flow()
}
}
Err(err) => Err(err), // structured OAuthTokenError (invalid_grant etc.) already surfaced by the library
} Prevention
- Exchange the authorization code immediately after redirect — codes are single-use and short-lived
- Make your token endpoint emit standard RFC 6749 section 5.2 JSON errors so the structured path gives precise diagnostics
- Ensure reverse proxies return 502/504 clearly and monitor them; this generic bail is how non-standard bodies show up
When it happens
Trigger: POST to token_endpoint with grant_type=authorization_code (+PKCE verifier) or refresh_token returns non-2xx and serde_json::from_str::<OAuthTokenError> fails on the body — e.g. 502 HTML from a reverse proxy, 400 with a plain-text 'invalid code', or 401 with an empty body.
Common situations: Authorization code expired or already used but server reports it non-standardly; clock skew breaking code/refresh-token validity windows; reverse proxy (nginx 502/504 HTML pages) between client and auth server; token endpoint requiring client auth the request does not send; server returns its error wrapped in a non-OAuth envelope so the structured parse path misses it.
Related errors
- DCR failed with status {}: {}
- OAuth endpoint must use HTTPS (got {}://{})
- OAuth endpoint must not point to private/reserved IP: {}
- OAuth endpoint must not point to private/reserved IP: ::ffff
- OAuth endpoint must not point to reserved IPv6 address: {}
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/3ca95f2990e5a177.
Report an issue: GitHub.