Hmbown/CodeWhale · error

OAuth revoke failed with HTTP

Error message

{} OAuth revoke failed with HTTP {status}: {}

What it means

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.

Solutions

  1. Verify the client_id matches the one that obtained the token (revoke endpoints reject mismatched clients)
  2. Read the compacted body in the message for the OAuth error code (e.g. invalid_client, unsupported_token_type)
  3. If the token was already revoked, treat a 400 as success and clear local state
  4. Retry on 5xx; investigate provider status if persistent
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure client_id matches the issuing client before revoking
if (clientId !== tokenIssuingClientId(provider)) {
  throw new Error('client_id does not match the client that issued this token');
}

Try / catch

try {
  await remoteRevoke(provider, issuer, clientId, token);
} catch (e) {
  if (String(e).includes('HTTP 400')) {
    clearLocalToken(provider); // some providers 400 on already-revoked tokens
  } else if (isRetryable(e)) {
    retryWithBackoff();
  } else { throw e; }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

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


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/558c5a3bdd0a9acb. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/oauth.rs:1046

    parse_oauth_form_response(status, &body, "refresh", params)
}

/// Best-effort remote revoke through the seam. Callers clear local
/// credentials regardless of this outcome.
pub(crate) fn revoke_remote_token_via(
    client: &dyn OAuthFormClient,
    params: &OAuthProviderParams,
    issuer: &str,
    client_id: &str,
    token: &str,
) -> Result<()> {
    let Some(revoke_url) = remote_revoke_url(params, issuer) else {
        bail!("{} has no remote revoke endpoint", params.display_name);
    };
    let (status, body) =
        client.post_form(&revoke_url, &[("token", token), ("client_id", client_id)])?;
    if !(200..300).contains(&status) {
        bail!(
            "{} OAuth revoke failed with HTTP {status}: {}",
            params.display_name,
            compact_form_error(&body)
        );
    }
    Ok(())
}

// ── PKCE browser login ────────────────────────────────────────────────

/// RFC 7636 S256 PKCE pair. Custom Debug: the verifier is exchanged for
/// bearer material and never prints.
#[derive(Clone)]
pub struct PkceChallenge {
    pub verifier: String,
    pub challenge: String,
}

View on GitHub (pinned to 73e0f67d83)