{"record":{"id":"558c5a3bdd0a9acb","repo":"Hmbown/CodeWhale","slug":"oauth-revoke-failed-with-http-status","errorCode":null,"errorMessage":"{} OAuth revoke failed with HTTP {status}: {}","messagePattern":"(.+?) OAuth revoke failed with HTTP (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/oauth.rs","lineNumber":1046,"sourceCode":"    parse_oauth_form_response(status, &body, \"refresh\", params)\n}\n\n/// Best-effort remote revoke through the seam. Callers clear local\n/// credentials regardless of this outcome.\npub(crate) fn revoke_remote_token_via(\n    client: &dyn OAuthFormClient,\n    params: &OAuthProviderParams,\n    issuer: &str,\n    client_id: &str,\n    token: &str,\n) -> Result<()> {\n    let Some(revoke_url) = remote_revoke_url(params, issuer) else {\n        bail!(\"{} has no remote revoke endpoint\", params.display_name);\n    };\n    let (status, body) =\n        client.post_form(&revoke_url, &[(\"token\", token), (\"client_id\", client_id)])?;\n    if !(200..300).contains(&status) {\n        bail!(\n            \"{} OAuth revoke failed with HTTP {status}: {}\",\n            params.display_name,\n            compact_form_error(&body)\n        );\n    }\n    Ok(())\n}\n\n// ── PKCE browser login ────────────────────────────────────────────────\n\n/// RFC 7636 S256 PKCE pair. Custom Debug: the verifier is exchanged for\n/// bearer material and never prints.\n#[derive(Clone)]\npub struct PkceChallenge {\n    pub verifier: String,\n    pub challenge: String,\n}\n","sourceCodeStart":1028,"sourceCodeEnd":1064,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/oauth.rs#L1028-L1064","documentation":"Thrown when the revocation endpoint replies with a non-2xx HTTP status. The body is compacted via `compact_form_error` so the developer sees a short reason (OAuth error code or truncated body) alongside the status.","triggerScenarios":"POSTing the token + client_id to the provider's revoke URL and receiving 4xx/5xx — e.g. `invalid_client` (wrong client_id), `unsupported_token_type`, 401/403 auth failure at the revocation endpoint, or provider 5xx.","commonSituations":"client_id not matching the one that issued the token (revocation endpoints often require the original client); token already revoked (some providers return 400); provider outage; auth misconfiguration for confidential clients.","solutions":["Verify the client_id matches the one that obtained the token (revoke endpoints reject mismatched clients)","Read the compacted body in the message for the OAuth error code (e.g. invalid_client, unsupported_token_type)","If the token was already revoked, treat a 400 as success and clear local state","Retry on 5xx; investigate provider status if persistent"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// ensure client_id matches the issuing client before revoking\nif (clientId !== tokenIssuingClientId(provider)) {\n  throw new Error('client_id does not match the client that issued this token');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await remoteRevoke(provider, issuer, clientId, token);\n} catch (e) {\n  if (String(e).includes('HTTP 400')) {\n    clearLocalToken(provider); // some providers 400 on already-revoked tokens\n  } else if (isRetryable(e)) {\n    retryWithBackoff();\n  } else { throw e; }\n}","preventionTips":["Always revoke with the same client_id that obtained the token","Treat 400 'already revoked' as success and clear local state","Retry 5xx revocation responses with backoff; alert on persistent failures"],"tags":["oauth","revocation","http"],"backgroundTag":"http-error-response","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}