{"record":{"id":"3ca95f2990e5a177","repo":"zed-industries/zed","slug":"token-request-failed-with-status-status-error","errorCode":null,"errorMessage":"token request failed with status {status}: {error_body}","messagePattern":"token request failed with status (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/context_server/src/oauth.rs","lineNumber":1036,"sourceCode":"\n    let request = Request::builder()\n        .method(http_client::http::Method::POST)\n        .uri(token_endpoint.as_str())\n        .header(\"Content-Type\", \"application/x-www-form-urlencoded\")\n        .header(\"Accept\", \"application/json\")\n        .body(AsyncBody::from(body.into_bytes()))?;\n\n    let mut response = http_client.send(request).await?;\n\n    if !response.status().is_success() {\n        let mut error_body = String::new();\n        response.body_mut().read_to_string(&mut error_body).await?;\n        let status = response.status();\n        // Try to parse as an OAuth error response (RFC 6749 Section 5.2).\n        if let Ok(token_error) = serde_json::from_str::<OAuthTokenError>(&error_body) {\n            return Err(token_error.into());\n        }\n        bail!(\"token request failed with status {status}: {error_body}\");\n    }\n\n    let mut response_body = String::new();\n    response\n        .body_mut()\n        .read_to_string(&mut response_body)\n        .await?;\n\n    let token_response: TokenResponse =\n        serde_json::from_str(&response_body).context(\"failed to parse token response\")?;\n\n    Ok(token_response.into_tokens())\n}\n\n// -- Loopback HTTP callback server -------------------------------------------\n\n/// An OAuth authorization callback received via the loopback HTTP server.\npub struct OAuthCallback {","sourceCodeStart":1018,"sourceCodeEnd":1054,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/context_server/src/oauth.rs#L1018-L1054","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before (non-standard error body from server)\nHTTP/1.1 400 Bad Request\n\ninvalid code\n\n# after (standard RFC 6749 error)\nHTTP/1.1 400 Bad Request\nContent-Type: application/json\n\n{\"error\":\"invalid_grant\",\"error_description\":\"authorization code expired\"}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match exchange_code(&client, &metadata, &code, &client_id, &redirect_uri, &verifier, &resource, secret).await {\n    Ok(tokens) => Ok(tokens),\n    Err(err) if err.to_string().contains(\"token request failed\") => {\n        let msg = err.to_string();\n        if msg.contains(\"5\") && msg.contains(\"Bad Gateway|Gateway|50\") {\n            // infrastructure error: safe to retry the exchange once with the same code\n            retry_once(small_backoff).await\n        } else {\n            // 4xx: the code/credentials are likely consumed or invalid — restart the flow, do NOT retry\n            restart_authorization_flow()\n        }\n    }\n    Err(err) => Err(err), // structured OAuthTokenError (invalid_grant etc.) already surfaced by the library\n}","preventionTips":["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"],"tags":["oauth","mcp","token","http-status","authorization-code","rfc-6749"],"backgroundTag":"oauth-token-request-failed","analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}