{"record":{"id":"8d93a3dd73bbb862","repo":"Hmbown/CodeWhale","slug":"has-no-remote-revoke-endpoint","errorCode":null,"errorMessage":"{} has no remote revoke endpoint","messagePattern":"(.+?) has no remote revoke endpoint","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/tui/src/oauth.rs","lineNumber":1041,"sourceCode":"            (\"grant_type\", \"refresh_token\"),\n            (\"client_id\", client_id),\n            (\"refresh_token\", refresh_token),\n        ],\n    )?;\n    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)]","sourceCodeStart":1023,"sourceCodeEnd":1059,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/oauth.rs#L1023-L1059","documentation":"Thrown by the remote revoke helper when `remote_revoke_url` returns None — the provider defines no revocation endpoint (RFC 7009), so the token cannot be revoked server-side. The caller should treat this as local-only removal.","triggerScenarios":"Calling the revoke path for a provider whose `oauth_provider_params` has no revocation endpoint and whose discovery metadata lacks `revocation_endpoint`.","commonSituations":"Provider that does not implement RFC 7009 token revocation; self-hosted IdP with revocation disabled; issuer discovery document without a revocation endpoint.","solutions":["Skip remote revocation and clear the locally stored token instead (treat as local logout)","Check whether the provider exposes a vendor-specific revoke endpoint and configure it","Enable the revocation endpoint on your identity provider if you control it"],"exampleFix":"// before\nremote_revoke(provider, issuer, client_id, token)?; // bails without endpoint\n// after\nif let Err(e) = remote_revoke(provider, issuer, client_id, token) {\n    if e.to_string().contains(\"no remote revoke endpoint\") {\n        clear_local_token(provider); // local logout only\n    } else {\n        return Err(e);\n    }\n}","handlingStrategy":"fallback","validationCode":"// check capability before revoking remotely\nconst url = remoteRevokeUrl(params, issuer);\nif (!url) {\n  clearLocalToken(provider); // local logout only\n  return;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await remoteRevoke(provider, issuer, clientId, token);\n} catch (e) {\n  if (String(e).includes('no remote revoke endpoint')) {\n    clearLocalToken(provider); // treat as local logout\n  } else { throw e; }\n}","preventionTips":["Check provider capability (revocation endpoint presence) before promising remote logout","Clearly label local-only logout in the UI when revocation is unsupported","Enable RFC 7009 on self-hosted IdPs if remote revocation is required"],"tags":["oauth","revocation","unsupported-feature"],"backgroundTag":"unsupported-operation","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"}