fix(auth): floor quota cooldown at the escalating ladder - #5130
Conversation
|
This pull request targeted The base branch has been automatically changed to |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 06c71872b9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c854d7c0f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fd030f7bec
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4227378efd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3955ad6fb6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4834e439a4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c898b95d6e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a9eee2b2cf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Follow-up for the mission review:
Checks on |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fba8015307
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Finding router-for-me#5130: ordinary throttling 429s ("Rate Limit Reached", "Too Many Requests", "Resource exhausted") with no quota signal were climbing the quota backoff ladder and putting live accounts into long bans. - Add isQuotaExhaustedResultError and isTransientThrottlingResultError. Quota 429s are detected by "quota"/"insufficient_quota"/"quota_exceeded" or a long Retry-After (>= 1m). - In applyAuthFailureState and the model-state 429 branch, transient 429s use recoverableFailureRetryAfter (or a short Retry-After) and mark Unavailable, but do not touch Quota.BackoffLevel or Quota.Exceeded. - Credential-scoped 429s and explicit quota 429s still propagate as quota. - Add TestApplyAuthFailureStateTransient429DoesNotEscalateQuotaBackoff. - Update TestManager_DeepSeekCredentialFailuresRotateCredential so "Rate Limit Reached" is transient; add a "Quota Exceeded" case for quota. - Update reports/4881-rebuild.md with the router-for-me#5130 finding and fix.
Finding router-for-me#5130: ordinary throttling 429s ("Rate Limit Reached", "Too Many Requests", "Resource exhausted") with no quota signal were climbing the quota backoff ladder and putting live accounts into long bans. - Add isQuotaExhaustedResultError and isTransientThrottlingResultError. Quota 429s are detected by "quota"/"insufficient_quota"/"quota_exceeded" or a long Retry-After (>= 1m). - In applyAuthFailureState and the model-state 429 branch, transient 429s use recoverableFailureRetryAfter (or a short Retry-After) and mark Unavailable, but do not touch Quota.BackoffLevel or Quota.Exceeded. - Credential-scoped 429s and explicit quota 429s still propagate as quota. - Add TestApplyAuthFailureStateTransient429DoesNotEscalateQuotaBackoff. - Update TestManager_DeepSeekCredentialFailuresRotateCredential so "Rate Limit Reached" is transient; add a "Quota Exceeded" case for quota. - Update reports/4881-rebuild.md with the router-for-me#5130 finding and fix.
Finding router-for-me#5130: ordinary throttling 429s ("Rate Limit Reached", "Too Many Requests", "Resource exhausted") with no quota signal were climbing the quota backoff ladder and putting live accounts into long bans. - Add isQuotaExhaustedResultError and isTransientThrottlingResultError. Quota 429s are detected by "quota"/"insufficient_quota"/"quota_exceeded" or a long Retry-After (>= 1m). - In applyAuthFailureState and the model-state 429 branch, transient 429s use recoverableFailureRetryAfter (or a short Retry-After) and mark Unavailable, but do not touch Quota.BackoffLevel or Quota.Exceeded. - Credential-scoped 429s and explicit quota 429s still propagate as quota. - Add TestApplyAuthFailureStateTransient429DoesNotEscalateQuotaBackoff. - Update TestManager_DeepSeekCredentialFailuresRotateCredential so "Rate Limit Reached" is transient; add a "Quota Exceeded" case for quota. - Update reports/4881-rebuild.md with the router-for-me#5130 finding and fix.
Finding from router-for-me#4881 review: internal/pluginhost/executor_route.go:248 `discardStreamChunks` started a goroutine that ranged over the source channel forever. If a plugin left the channel open after an empty terminal frame, the drainer goroutine never exited, causing a cumulative leak in a long-lived proxy. - Pass the request context into `discardStreamChunks`. - Add `streamDrainTimeout` (5s default); the drainer exits on context cancellation, on timeout, or when the source channel closes. - Reset the drain timeout on each received chunk so slow trailing chunks are still drained, but the goroutine is always bounded. - Add `TestDiscardStreamChunksExitsOnContextCancel` and `TestDiscardStreamChunksExitsOnOpenUnclosedChannel` proving the goroutine exits even when the source channel is never closed. - Update reports/4881-rebuild.md with the take/no-take findings and router-for-me#5130 status.
… and timeout `sdk/cliproxy/auth/conductor_stream.go` had its own `discardStreamChunks` variant that started a goroutine ranging over the source channel forever. Without a context, a plugin that left the channel open leaked a goroutine per stream. - Add `ctx` parameter and `streamDrainTimeout` (5s default) to the auth `discardStreamChunks`, mirroring the pluginhost fix. - The drainer exits on context cancellation, on timeout, or when the source channel closes; the timeout resets on each received chunk so slow trailing chunks are still drained. - Update all call sites in `conductor_stream.go` to pass the request context. - Add `TestDiscardStreamChunksExitsOnContextCancel` and `TestDiscardStreamChunksExitsOnOpenUnclosedChannel` in `conductor_stream_test.go`. Also removes the transient-429 changes from the router-for-me#4881 branch; that finding belongs on router-for-me#5130 (fix/quota-backoff-hint-floor).
Rebase the router-for-me#5130 change onto current origin/dev. A provider Retry-After hint may push the recovery deadline later, but must not undercut the quota ladder or the 10s transient floor. The original hint is still proxied to the client. Transient 429s stay off the exhausted-quota ladder. Token-count errors go through newAntigravityStatusErr so the same classification reaches the conductor.
e9ac9fb to
b72a9c8
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b72a9c84b4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
A hintless classified 429 with transient cooldowns disabled was clearing Unavailable and NextRetryAfter on the model even when a 401/403/404/5xx window was already open. Restore the pre-result availability and retry deadline, matching applyAuthFailureState.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 69f8e1baf3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
wrapStreamResult dropped RetryAfter and TransientRateLimit from post-bootstrap chunk errors, so a classified 429 after the first payload advanced the exhausted-quota ladder.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 09eeca8028
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Non-positive RetryAfter on a non-transient 429 undercut the quota ladder (Antigravity 0s). Keep the bypass for classified transients only.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0dd09f8bc6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Skip-path guards tested Exceeded, which stays true after NextRecoverAt. An expired record then vetoed skip for a hintless transient 429 with cooldowns disabled. Check deadline activity at model and auth level.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d736dc686c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Zero-delay Retry-After on websocket_connection_limit_reached is not a quota signal. After the bypass required TransientRateLimit, this path climbed the exhausted-quota ladder. Mark it transient so the conductor keeps the zero-delay path; usage_limit 429s stay unchanged.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 54c809b590
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
oauth2.googleapis.com 429 is a token-endpoint throttle, not model quota. Unclassified, it climbed the exhausted-quota ladder. Mark it transient; Retry-After stays as-is.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 061aaa0de5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Prepare-error Results dropped RetryAfter and TransientRateLimit, so a classified refresh 429 during request preparation climbed the quota ladder. Copy both fields like the execute path.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 75aef5f5a9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| next = time.Time{} | ||
| transientCooldownOff = true | ||
| } else { | ||
| next, backoffLevel = transientRateLimitCooldownAfterFailure(state.Quota, result.RetryAfter, now) |
There was a problem hiding this comment.
Keep transient cooldowns separate from quota windows
When a classified transient 429 takes this branch, the common code below still stores the deadline as Quota.Exceeded with reason quota. If a genuine exhausted-quota 429 arrives before this 10-second transient window expires, quotaCooldownAfterFailure treats that transient deadline as an existing quota window and reuses it instead of applying the one-minute ladder floor. The model path also calls SetModelQuotaExceeded, whose registry marker lasts five minutes and can make GetFirstAvailableModel report no available client for model: "auto" long after the transient cooldown expires. Store or tag transient cooldowns separately so exhausted-quota handling and registry quota accounting do not consume them.
Useful? React with 👍 / 👎.
Summary
Gemini and Antigravity can answer HTTP 429 for a fully exhausted daily quota with a
RetryInfohint of only ~479ms (observed:479417207ns). Taking that hint verbatim returned the dead credential to the pool ~500ms later AND pinnedBackoffLevelforever because every retry recomputed the ladder and then discarded it in favor of the sub-second hint.This change floors the quota cooldown deadline at the escalating ladder calculation. A provider hint may still push the recovery deadline further out, but can never pull it in below the ladder step — except for the two cases the provider itself marks as non-exhaustion (see the follow-up rounds below).
Code changes
Before
After
Blast radius
quotaCooldownAfterFailureis package-private with exactly two call sites:MarkResultinsdk/cliproxy/auth/conductor_cooldown.goapplyAuthFailureStateinsdk/cliproxy/auth/conductor_cooldown.goBoth call sites are updated identically to respect the ladder floor.
Tests & Verification
Added two unit tests in
sdk/cliproxy/auth/cooldown_backoff_test.go:TestMarkResultSubSecondQuotaHintStillEscalatesTestApplyAuthFailureStateSubSecondQuotaHintStillEscalatesMandatory reverse bite-check failure
Reverting the fix produced the expected test failure:
Passing tests with fix applied
Follow-up review round 1 — zero-delay retries (
4c854d7c)Review pointed out that an unconditional floor also swallowed
RetryAfter: 0, which upstreamuses to mean "retry immediately on the same credential". The gate now exempts a non-positive
hint (
conductor_cooldown.go:829inMarkResult,:1966inapplyAuthFailureState).Tests:
TestMarkResultZeroRetryAfterDoesNotApplyLadderFloor,TestApplyAuthFailureStateZeroRetryAfterDoesNotApplyLadderFloor.Reverse bite-check — dropping the
*retryAfter <= 0clause from both gates:Follow-up review round 2 — transient rate limits (
fd030f7b)Review then pointed out that the floor still applied to 429s the executor had already
classified as a short-lived rate limit rather than an exhausted quota, parking a usable
credential for a whole ladder step.
classifyAntigravity429(internal/runtime/executor/antigravity_executor_credits.go:203) alreadydistinguishes
RATE_LIMIT_EXCEEDEDfromQUOTA_EXHAUSTED, but the classification never reachedthe conductor:
newAntigravityStatusErrbuilt a barestatusErr{code, msg, retryAfter}and onlystatus, text and hint were carried across.
statusErrgainstransientRateLimit boolandfunc (e statusErr) TransientRateLimit() bool(
internal/runtime/executor/openai_compat_executor.go:1031). Every other executor leaves itfalse, so their behaviour is unchanged.newAntigravityStatusErrsets it for 429s only:err.transientRateLimit = classifyAntigravity429(body) == antigravity429RateLimited(
antigravity_executor_credits.go:344). Deliberately narrow:QUOTA_EXHAUSTED, soft bodiesand anything unclassified keep the floor and keep escalating.
isTransientRateLimitError(sdk/cliproxy/auth/conductor_cooldown.go:1459) recovers the flagwith
errors.Asover aninterface{ TransientRateLimit() bool }, mirroring howretryAfterFromErroralready recovers the hint. The execution paths setresult.TransientRateLimitnext to the existingresult.RetryAfterassignment.The flag lives on
Result, not onauth.Error, and is threaded as the sixth parameter ofapplyAuthFailureState:sdk/cliproxy/auth/errors_compat_test.goTestErrorLegacyUnkeyedLiteralCompatibilityconstructsErrorwith an unkeyed literal, so anynew field on that struct breaks
go vet. The seven pre-existingapplyAuthFailureStatecallsites in
cooldown_backoff_test.gowere updated mechanically with a trailing, false; noassertion was changed.
Tests:
TestMarkResultTransientRateLimitKeepsProviderHint,TestApplyAuthFailureStateTransientRateLimitKeepsProviderHint(its second half feeds the samesub-second hint through an exhausted-quota error and asserts the floor and
BackoffLevel == 1still apply),
TestIsTransientRateLimitErrorDetectsWrappedProviderClassification,TestNewAntigravityStatusErrMarksTransientRateLimit.Reverse bite-check — reverting both gates to
*retryAfter <= 0:Reverse bite-check — discarding the executor classification:
go build ./...,go vet ./sdk/cliproxy/... ./internal/runtime/executor/...,gofmt -landgo test ./sdk/cliproxy/auth/... ./internal/runtime/executor/...are clean.Follow-up review round 3 — streaming and token-count paths (
4227378e)Review found the classification was still only wired into the non-stream execution path.
executeStreamWithModelPoolbuilt every failure result fromretryAfterFromErroralone. Allfive results now set the flag next to the hint:
sdk/cliproxy/auth/conductor_stream.go:275,:354,:374,:386,:401. The mid-stream result inwrapStreamResult(:131) is leftalone on purpose — it sets no hint today, so the ladder still applies there and adding one
would be a behavioural change beyond this review.
CountTokenshand-rolledstatusErrtwice. Both now go throughnewAntigravityStatusErr(internal/runtime/executor/antigravity_executor_tokens.go:167and:172), which does the samehelps.ParseRetryDelaywork and additionally appliesclassifyAntigravity429.Tests:
TestExecuteStreamKeepsProviderHintForTransientRateLimit(new filesdk/cliproxy/auth/conductor_stream_classification_test.go, drives a realManager.ExecuteStreamagainst a stub executor whose error reports 429 +
479417207ns+ transient) andTestAntigravityCountTokensClassifiesTransient429(realCountTokensagainst anhttptestserver serving a structured
RATE_LIMIT_EXCEEDED429).Reverse bite-check — dropping the new line from the executor-error stream branch:
Reverse bite-check — restoring the hand-rolled token-count
statusErr:Downstream counterpart: kaitranntt/CLIProxyAPIPlus#198
Review follow-up: short-cooldown 429s are transient
The Antigravity executor raises a synthetic 429 while an auth sits in a short cooldown. That cooldown is a local, self-imposed pause of at most a few minutes, but the error carried only a positive
retryAfterhint and no classification, soisTransientRateLimitError()(sdk/cliproxy/auth/conductor_cooldown.go:1459) returned false andapplyAuthFailureState()(sdk/cliproxy/auth/conductor_cooldown.go:914) treated it as an exhausted upstream quota, escalatingBackoffLeveltoward the 30 minute ceiling.All three cooldown short-circuits now set
transientRateLimit: true—internal/runtime/executor/antigravity_executor_execute.go:36(Execute),:268(executeClaudeNonStream) andinternal/runtime/executor/antigravity_executor_stream.go:35(ExecuteStream) — because the classification is consumed independently on each path (conductor_execution.go:381,conductor_stream.go:275/354/374/386/401,conductor_home_execution.go:181).Test:
TestAntigravityShortCooldownErrorIsTransientininternal/runtime/executor/antigravity_executor_cooldown_transient_test.go.Reverse bite-check — dropping
transientRateLimit: truefrom the three literals:Review follow-up: ordinary Claude 429s are transient
Ordinary (non-unified) Claude 429s reached
claudeRateLimitErrorwrapping astatusErrwith notransientRateLimitflag (internal/runtime/executor/claude_executor_request.go:295-308), soisTransientRateLimitError()returned false andapplyAuthFailureState()treated an ordinary model-level throttle as exhausted quota, escalatingBackoffLeveltoward the 30 minute ceiling. The ordinary path inclassifyClaudeUpstreamErrornow setstransientRateLimit = true; the unified 5h/7d rejection path is untouched and stays on the quota ladder.Tests:
TestClassifyClaudeUpstreamError_OrdinaryRateLimitIsTransientandTestClassifyClaudeUpstreamError_UnifiedRejectionNotTransient(the negative pin for the unified path).Reverse bite-check — dropping
err.transientRateLimit = true:Review follow-up: transient 429s without a hint bypass the quota ladder
The ladder bypass previously required a parseable
retryAfterhint, so a transient 429 with no hint (e.g. an ordinary Claude throttle without reset headers) fell intoquotaCooldownAfterFailureand advancedBackoffLevelanyway. Both copies of the logic —MarkResult's per-model state (sdk/cliproxy/auth/conductor_cooldown.go:829) andapplyAuthFailureState's credential state (:1917) — now bypass the ladder for any transient 429, keeping the hint verbatim when present and falling back tonextTransientErrorRetryAfter(~60s) otherwise.Test:
TestManager_MarkResult_Transient429WithoutHintBypassesQuotaLadder.Reverse bite-check — reverting
conductor_cooldown.go:Review follow-up: transient fallback respects disabled cooldowns
With
transientErrorCooldownSeconds < 0the transient fallback returns a zero time, but both 429 branches still recordedUnavailable=true/Quota.Exceeded=truewith an emptyNextRecoverAt, whichavailabilityBlockreads as an indefinite park. The transient-429 handling inMarkResult(sdk/cliproxy/auth/conductor_cooldown.go:861) andapplyAuthFailureState(:1952) now skips the marking when the transient fallback yields a zero time, preserving any pre-existing quota block.Test:
TestManager_MarkResult_Transient429WithoutHintRespectsDisabledCooldown.Reverse bite-check — reverting
conductor_cooldown.go:Review follow-up: disabled-cooldown skip restores availability fields
The transient-cooldown-off skip restored the status/quota fields but left
auth.Unavailable=true(set at the top ofapplyAuthFailureState) andauth.NextRetryAfteruntouched, so the credential stayed blocked anyway. The prior availability fields are now captured and restored (sdk/cliproxy/auth/conductor_cooldown.go:2027).Test:
TestManager_MarkResult_Transient429WithoutHintRespectsDisabledCooldownAuthLevel(auth-level Result drivesapplyAuthFailureState).Reverse bite-check — reverting
conductor_cooldown.go:Review follow-up: reasoned RATE_LIMIT_EXCEEDED without RetryInfo is transient
RATE_LIMIT_EXCEEDEDwithout aRetryInfodetail downgrades toSoftRetry, and only theRateLimitedcategory was marked transient, so a plain per-minute throttle was read as exhausted quota.newAntigravityStatusErrnow marks the soft rate limit transient when the classification comes from the ErrorInfo reason (antigravity_executor_credits.go:350); the bare "too many requests" message heuristic still stays on the quota ladder.Test: new case in
TestNewAntigravityStatusErrMarksTransientRateLimit.Reverse bite-check — reverting
antigravity_executor_credits.go:Tooling note
The
jbcontextCLI is installed on this machine but its stored session cannot be decrypted (the OS keychain is not accessible), sojbcontext searchcould not run. The equivalent semantic search, review and blast-radius passes were performed with local code-intelligence tooling instead.