fix(antigravity): rotate image accounts on explicit quota exhaustion - #9908
Conversation
3554bb2 to
8946a06
Compare
|
Clean, well-scoped fix — reusing the existing |
…ation Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
e2c9bec to
58bd92e
Compare
Merged after batch validation on a combined worktree cut from `release/v3.8.51` with #9908 and #7138. **Evidence** - Focused tests: 36/36 pass on the combined tree (`oauth-modal-codex-lan-ip-8046`, `antigravity-image-credential-retry`, `antigravity-usage-service`, `generic-quota-fetcher`), including the 2 pre-existing anchor tests that assert `codex` stays in `PKCE_CALLBACK_SERVER_PROVIDERS` and that the `localhost:1455` redirect URI is untouched. - Gates on the combined tree: `check-complexity` PASS, `check-cognitive-complexity` PASS, `typecheck:core` PASS, `check-changelog-integrity` PASS. - `check-file-size` is red, but reproduces byte-identical on the pure `release/v3.8.51` tip (`imageGeneration.ts`, `roundRobinCombo.ts`, `stream.ts`) — inherited base-red, not from this PR. **Reconciled** - `changelog.d/fixes/codex-manual-loopback-action.md` did not start with a markdown bullet, which is the one thing `check-changelog-integrity` failed on. Fixed in your branch (d3dbb5b) so the fragment convention holds; nothing else in your diff was touched. Thanks for this one, @Ardem2025 — exposing the manual callback entry that already existed in the code instead of adding a new flow is exactly the right shape for the LAN/remote case, and keeping every PKCE/state check untouched made it easy to verify.
00860f3
into
diegosouzapw:release/v3.8.51
Merged after batch validation on a combined worktree cut from `release/v3.8.51` with #9944 and #9908. **Evidence** - Focused tests: 36/36 pass on the combined tree, including `convertUsageToQuotaInfo skips Antigravity quota entries with an unknown fraction` and the `#6295` regression that guards the same class of bug on another provider. - Gates on the combined tree: `check-complexity` PASS, `check-cognitive-complexity` PASS, `typecheck:core` PASS, `check-changelog-integrity` PASS. - The red `check-file-size` reproduces byte-identical on the pure `release/v3.8.51` tip — inherited base-red, not from this PR. The red CI run here dates from 2026-09-15 against an older base. Thanks, @Ardem2025 — this is the smallest diff of your batch and arguably the one with the widest blast radius avoided. Writing `remainingPercentage: 0` for an unreported fraction made "we don't know" numerically indistinguishable from "fully exhausted" to every downstream consumer of the quota cache; omitting the field so preflight fails open is the correct read of the upstream's silence.
Merged after batch validation on a combined worktree cut from `release/v3.8.51` with diegosouzapw#9908 and diegosouzapw#7138. **Evidence** - Focused tests: 36/36 pass on the combined tree (`oauth-modal-codex-lan-ip-8046`, `antigravity-image-credential-retry`, `antigravity-usage-service`, `generic-quota-fetcher`), including the 2 pre-existing anchor tests that assert `codex` stays in `PKCE_CALLBACK_SERVER_PROVIDERS` and that the `localhost:1455` redirect URI is untouched. - Gates on the combined tree: `check-complexity` PASS, `check-cognitive-complexity` PASS, `typecheck:core` PASS, `check-changelog-integrity` PASS. - `check-file-size` is red, but reproduces byte-identical on the pure `release/v3.8.51` tip (`imageGeneration.ts`, `roundRobinCombo.ts`, `stream.ts`) — inherited base-red, not from this PR. **Reconciled** - `changelog.d/fixes/codex-manual-loopback-action.md` did not start with a markdown bullet, which is the one thing `check-changelog-integrity` failed on. Fixed in your branch (d3dbb5b) so the fragment convention holds; nothing else in your diff was touched. Thanks for this one, @Ardem2025 — exposing the manual callback entry that already existed in the code instead of adding a new flow is exactly the right shape for the LAN/remote case, and keeping every PKCE/state check untouched made it easy to verify.
…iegosouzapw#9908) Merged after batch validation on a combined worktree cut from `release/v3.8.51` with diegosouzapw#9944 and diegosouzapw#7138. **Evidence** - Focused tests: 36/36 pass on the combined tree, including your 4 classification-boundary cases in `tests/unit/antigravity-image-credential-retry.test.ts` (Antigravity quota-exhausted `429` rotates; generic `RESOURCE_EXHAUSTED`, ordinary image rate-limit and non-Antigravity `429` stay terminal). - Gates on the combined tree: `check-complexity` PASS, `check-cognitive-complexity` PASS, `typecheck:core` PASS, `check-changelog-integrity` PASS. - The red `check-file-size` reproduces byte-identical on the pure `release/v3.8.51` tip — inherited base-red, not from this PR. The red CI run on this PR dates from 2026-09-15 against an older base. **Related dispositions** - diegosouzapw#8053 is being closed in your favour: it chased the same account-rotation goal across 3 files plus a new `routingInstrumentation.ts`, while its `AbortSignal` half was already superseded on the tip by independent work. This PR does the same job in 31 lines of production code by reusing the existing `classify429` engine. Thanks, @Ardem2025 — the deliberate narrowness here is the reason this merged and the bigger version didn't. Gating rotation on `provider === "antigravity" && status === 429 && classify429() === "quota_exhausted"` keeps non-idempotent image generation from being retried on ordinary rate limits, and you proved each negative case rather than just the happy path.
…w#7138) Merged after batch validation on a combined worktree cut from `release/v3.8.51` with diegosouzapw#9944 and diegosouzapw#9908. **Evidence** - Focused tests: 36/36 pass on the combined tree, including `convertUsageToQuotaInfo skips Antigravity quota entries with an unknown fraction` and the `diegosouzapw#6295` regression that guards the same class of bug on another provider. - Gates on the combined tree: `check-complexity` PASS, `check-cognitive-complexity` PASS, `typecheck:core` PASS, `check-changelog-integrity` PASS. - The red `check-file-size` reproduces byte-identical on the pure `release/v3.8.51` tip — inherited base-red, not from this PR. The red CI run here dates from 2026-09-15 against an older base. Thanks, @Ardem2025 — this is the smallest diff of your batch and arguably the one with the widest blast radius avoided. Writing `remainingPercentage: 0` for an unreported fraction made "we don't know" numerically indistinguishable from "fully exhausted" to every downstream consumer of the quota cache; omitting the field so preflight fails open is the correct read of the upstream's silence.
Summary
429, genericRESOURCE_EXHAUSTED, and non-Antigravity failures remain terminal.401responses.Why
OmniRoute already refreshes OAuth credentials and rotates image accounts after
401, but an Antigravity account with an explicitly exhausted image quota returns429. The request currently stops even when another configured Antigravity account still has quota.Image generation may be non-idempotent, so retrying every
429would be unsafe. This change reuses the existing Antigravity 429 classifier and permits rotation only when:antigravity;429; andclassify429(error) === "quota_exhausted".Generic rate limits, malformed/system-payload errors, and non-Antigravity responses do not rotate accounts.
Related Issues
401)Validation
node --import tsx/esm --test tests/unit/antigravity-image-credential-retry.test.tsnpm run lintValidation was run from a clean clone on Node
v22.22.3/ npm10.9.8.Tests Added Or Updated
tests/unit/antigravity-image-credential-retry.test.tsIndividual quota reachedresponse;RESOURCE_EXHAUSTEDresponse;Coverage Notes
The new focused unit file directly covers the exported retry predicate and its positive and negative classification boundaries. The retry coordinator uses the same predicate to decide whether another credential may be selected.
Reviewer Notes