fix(auth): distinguish revoked API keys from transient auth errors (#25754)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 8f9c07a200
Co-authored-by: rrenamed <87486610+rrenamed@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
Aleksandrs Tihenko
2026-02-26 02:47:16 +02:00
committed by GitHub
parent f312222159
commit c0026274d9
15 changed files with 247 additions and 18 deletions

View File

@@ -649,6 +649,14 @@ const ERROR_PATTERNS = {
"plans & billing",
"insufficient balance",
],
authPermanent: [
/api[_ ]?key[_ ]?(?:revoked|invalid|deactivated|deleted)/i,
"invalid_api_key",
"key has been disabled",
"key has been revoked",
"account has been deactivated",
/could not (?:authenticate|validate).*(?:api[_ ]?key|credentials)/i,
],
auth: [
/invalid[_ ]?api[_ ]?key/,
"incorrect api key",
@@ -755,6 +763,10 @@ export function isBillingAssistantError(msg: AssistantMessage | undefined): bool
return isBillingErrorMessage(msg.errorMessage ?? "");
}
export function isAuthPermanentErrorMessage(raw: string): boolean {
return matchesErrorPatterns(raw, ERROR_PATTERNS.authPermanent);
}
export function isAuthErrorMessage(raw: string): boolean {
return matchesErrorPatterns(raw, ERROR_PATTERNS.auth);
}
@@ -899,6 +911,9 @@ export function classifyFailoverReason(raw: string): FailoverReason | null {
if (isTimeoutErrorMessage(raw)) {
return "timeout";
}
if (isAuthPermanentErrorMessage(raw)) {
return "auth_permanent";
}
if (isAuthErrorMessage(raw)) {
return "auth";
}

View File

@@ -2,6 +2,7 @@ export type EmbeddedContextFile = { path: string; content: string };
export type FailoverReason =
| "auth"
| "auth_permanent"
| "format"
| "rate_limit"
| "billing"