fix(auth): distinguish revoked API keys from transient auth errors#25754
fix(auth): distinguish revoked API keys from transient auth errors#25754gumadeiras merged 3 commits intoopenclaw:mainfrom
Conversation
Design note: why
|
c66546c to
aadd7cf
Compare
aadd7cf to
cb1b9b4
Compare
ec3fe7b to
8f9c07a
Compare
…penclaw#25754) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8f9c07a Co-authored-by: rrenamed <87486610+rrenamed@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…penclaw#25754) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8f9c07a Co-authored-by: rrenamed <87486610+rrenamed@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…penclaw#25754) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8f9c07a Co-authored-by: rrenamed <87486610+rrenamed@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…penclaw#25754) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8f9c07a Co-authored-by: rrenamed <87486610+rrenamed@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…penclaw#25754) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8f9c07a Co-authored-by: rrenamed <87486610+rrenamed@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…penclaw#25754) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8f9c07a Co-authored-by: rrenamed <87486610+rrenamed@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…penclaw#25754) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8f9c07a Co-authored-by: rrenamed <87486610+rrenamed@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…penclaw#25754) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8f9c07a Co-authored-by: rrenamed <87486610+rrenamed@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
…penclaw#25754) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8f9c07a Co-authored-by: rrenamed <87486610+rrenamed@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
Summary
resolveFailoverReasonFromError()maps HTTP 401/403 to"auth"with a transient cooldown (max 1h). The key retries forever instead of being permanently disabled."auth_permanent"as a newFailoverReasonvariant. Provider-specific permanent auth error signals (e.g.invalid_api_key,key has been revoked) are detected in the error message and routed through thedisabledUntilpath (5h default, 24h max — same as billing) instead of the transientcooldownUntilpath."unauthorized","forbidden","invalid token", expired tokens) still use the existing"auth"reason with short cooldown. No changes to billing error handling. No changes to UI/display code.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
models statuswill showdisabledUntil+disabledReason: "auth_permanent"for affected profiles.Security Impact (required)
NoNoNoNoNoRepro + Verification
Environment
Steps
"invalid_api_key"in the error messageusageStatsfor the profileExpected
disabledUntilset (5h+ window),disabledReason: "auth_permanent"cooldownUntil(transient path)Actual
cooldownUntil(max 1h), retries foreverdisabledUntil(5h default, 24h max), matching billing error behaviorEvidence
failover-error.test.ts(6 tests),auth-profiles.markauthprofilefailure.test.ts(1 test),pi-embedded-helpers.isbillingerrormessage.test.ts(3 test blocks)pnpm check(oxfmt + tsgo + oxlint) cleanHuman Verification (required)
"auth"), permanent auth detection from status+message, pattern separation ("invalid api key"→ auth vs"invalid_api_key"→ auth_permanent),disabledUntilpath for auth_permanent,clearExpiredCooldownshandles auth_permanent generically,markAuthProfileUsedclears disabled state on success"auth"),FAILURE_REASON_SET/FAILURE_REASON_ORDERauto-updated,as consttype inference,pi-embedded-runner/run.tspropagation,list.probe.ts/list.status-command.tsfallthrough behavior"invalid_api_key"flow in a running instance)Compatibility / Migration
YesNoNoFailure Recovery (if this breaks)
"auth_permanent"reason will simply never be produced, falling back to"auth"transient cooldownauthPermanentpatterns), or profiles NOT being disabled when they should be (pattern too narrow)Risks and Mitigations
authPermanentpatterns could false-positive on messages that contain"invalid_api_key"as substring in a longer non-permanent error"invalid_api_key", regexes requiringrevoked|deactivated|deletedafterapi key). Transient auth keywords ("unauthorized","forbidden","invalid token") are NOT in the permanent list. The permanent check runs only when HTTP status is already 401/403 or when message-only classification is used.Greptile Summary
This PR adds a new
"auth_permanent"failover reason to distinguish permanently revoked/invalid API keys from transient auth errors. Revoked keys now trigger the longerdisabledUntilpath (5h default, 24h max) instead of the shortercooldownUntilpath (max 1h), preventing indefinite retry loops during key rotation.The implementation follows existing patterns:
authPermanentbeforeauthinclassifyFailoverReason()to handle overlapping patterns correctlyauth_permanentreuses the billing backoff logic (disabledUntil+ exponential backoff)FAILURE_REASON_PRIORITYplacesauth_permanentat highest priority (beforeauth)Confidence Score: 5/5
"auth"), and includes proper ordering to handle pattern overlaps. The implementation correctly reuses billing backoff logic for permanent auth failures.Last reviewed commit: c66546c