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

  1. Inspect the {status} in the message: 5xx usually means a backend/proxy failure — check auth server logs and proxy health before touching client config
  2. 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
  3. 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)
  4. 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

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


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