Skip to content

fix(rate-limits): refresh Antigravity usage and show quota groups#8735

Open
leomleao wants to merge 4 commits into
stablyai:mainfrom
leomleao:leomleao/fix-antigravity-keyring-usage
Open

fix(rate-limits): refresh Antigravity usage and show quota groups#8735
leomleao wants to merge 4 commits into
stablyai:mainfrom
leomleao:leomleao/fix-antigravity-keyring-usage

Conversation

@leomleao

@leomleao leomleao commented Jul 14, 2026

Copy link
Copy Markdown

Summary

Fix Antigravity usage refreshes and show the quota structure reported by Antigravity itself.

  • Prefer the running agy CLI's loopback quota service, then the Antigravity desktop language server, native OS keyring credentials, and existing Gemini OAuth sources.
  • Read Antigravity's native keyring entry on macOS, Linux, and Windows so an expired legacy Gemini token no longer causes Refresh failed while Antigravity remains signed in.
  • Parse the authoritative quota summary into separate Gemini models and Claude and GPT models groups, each with five-hour and weekly meters.
  • Keep the compact status-bar value compatible by deriving it from the most constrained window across the groups.
  • Update the opt-in setting copy to describe the CLI-first behavior and its fallbacks accurately.

The root cause was twofold: Antigravity moved its current OAuth state into the native OS keyring, while Orca still preferred an expired legacy Gemini credential; the legacy per-model quota endpoint also does not expose Antigravity's current grouped five-hour/weekly limits.

Screenshots

Verified in a running Orca build: the Antigravity popover refreshes successfully and renders Gemini models plus Claude and GPT models, each with five-hour and weekly usage.
image

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test — 2,802 files / 29,651 tests passed; 7 files / 43 tests skipped
  • pnpm build
  • Added or updated high-quality tests that would catch regressions

Coverage includes native keyring command selection and credential parsing, current/expired credential precedence, grouped quota parsing, missing reset metadata, CLI-first fetch precedence, stale-newer-log fallback to a live older AGY session, localized settings copy, and grouped popover rendering.

AI Review Report

Reviewed the complete diff against the recurring feedback on #8132 and the current rate-limit/status-bar PRs.

  • Stale/latest state: listener parsing uses the newest announcement within a log, tries logs newest-first, and continues past stale one-shot AGY logs. A loopback integration test proves a newer stale log cannot hide a live CLI session.
  • Initial and fallback state: the existing rate-limit refresh lifecycle hydrates the renderer snapshot; failed local discovery falls through without replacing usable credential sources, and the existing stale-snapshot policy preserves the grouped data after transient refresh failures.
  • Determinism: parser tests use fixed timestamps/explicit updatedAt; no assertion depends on wall-clock time.
  • Localization: all new visible group, window, setting, and search copy uses hash-keyed translate(...) entries with catalog parity for en/es/ja/ko/zh. The audit caught and fixed the AccountsPane runtime-label contract before publication.
  • Cross-platform: keyring access uses fixed execFile argument arrays for macOS security, Linux/BSD secret-tool, and Windows Credential Manager via a constant PowerShell script. Paths use Node path utilities; no shortcut, shell interpolation, or separator assumptions were added. Usage remains scoped to the desktop host, including when worktrees are reached over SSH.
  • UI: the grouped panel reuses existing status-bar typography, spacing, progress bars, tokens, and percentage-display preferences.

Relevant open-PR review: #8669 touches the same generic tooltip, shared rate-limit types, and locale catalogs for Cursor usage. There is no behavioral dependency, but whichever PR merges second may need a straightforward rebase in those files. #8717 and #8643 are in the same rate-limit domain without file overlap.

Security Audit

  • Native keyring commands are fixed, invoked without a shell, bounded to a 3-second timeout and 16 KiB output, and never log credential material.
  • Local quota discovery connects only to 127.0.0.1, sends no OAuth credentials to the CLI service, uses a 3-second timeout and 1 MiB response cap, and validates the returned quota schema.
  • The self-signed HTTPS exception is restricted to the fixed loopback host. Desktop CSRF configuration is accepted only from a page identifying itself as Antigravity and is returned only to that same loopback endpoint.
  • User-controlled log contents can select only a validated loopback port; they cannot redirect requests to another host. Failures are isolated and fall through to existing credential sources.
  • No new dependency, renderer-supplied command, persistent secret, IPC channel, or external network destination is added.

Notes

  • agy does not expose a public /usage command. Orca reads the already-running CLI's local quota service instead of launching an interactive login flow. The Antigravity desktop app is optional and serves only as a fallback.
  • The feature remains behind the existing experimental Antigravity/Gemini usage opt-in.
  • The branch is based on the current stablyai/orca main at 9d5173252.

@leomleao leomleao marked this pull request as ready for review July 14, 2026 12:17
@leomleao

Copy link
Copy Markdown
Author

This change was needed because Gemini CLI is no longer available with Pro subscriptions, with Google directing users toward Antigravity CLI instead. As a result, Orca can no longer rely on Gemini as a proxy for Antigravity quota information. Reading usage directly from Antigravity fixes the refresh failure and exposes the actual five-hour and weekly limits across the Gemini, Claude, and GPT model groups.

image

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds Antigravity local quota discovery through CLI and desktop logs, quota-summary parsing, and platform-specific OAuth keyring access. Gemini rate-limit fetching now prefers local Antigravity data and credentials before legacy sources. Shared rate-limit types support grouped windows, and the status bar renders them with updated tests and localized settings copy.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main Antigravity rate-limit refresh and grouped quota display changes.
Description check ✅ Passed It includes all required sections and details, including screenshots, testing, AI review, security audit, and notes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/main/rate-limits/antigravity-oauth-keyring.ts (1)

17-98: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Windows target name is hardcoded separately from the shared constants.

The PowerShell script hardcodes 'gemini:antigravity' (Line 47) instead of deriving it from KEYRING_SERVICE/KEYRING_ACCOUNT, which are used for the macOS/Linux commands. If either constant ever changes, the Windows lookup will silently target the wrong credential (verified service:username is the correct zalando/go-keyring Windows format, so today's value is correct — but the duplication is a latent drift risk).

♻️ Derive the Windows target from the shared constants
 const WINDOWS_CREDENTIAL_SCRIPT = String.raw`
 $signature = @'
 ...
 '@
 Add-Type -TypeDefinition $signature
 $pointer = [IntPtr]::Zero
-if ([OrcaCredentialReader]::CredRead('gemini:antigravity', 1, 0, [ref]$pointer)) {
+if ([OrcaCredentialReader]::CredRead('__TARGET__', 1, 0, [ref]$pointer)) {
   ...
 }
-`.trim()
+`.trim()
+
+function buildWindowsCredentialScript(target: string): string {
+  return WINDOWS_CREDENTIAL_SCRIPT.replace('__TARGET__', target)
+}

Then call getAntigravityKeyringCommand with args: ['-NoProfile', '-NonInteractive', '-Command', buildWindowsCredentialScript(${KEYRING_SERVICE}:${KEYRING_ACCOUNT})].

src/main/rate-limits/antigravity-oauth-keyring.test.ts (1)

1-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for readAntigravityCredentials.

Only the pure helper functions are tested; the exported readAntigravityCredentials (execFile call, timeout/maxBuffer options, base64-decode branch for Windows output, and catch-to-null fallback) has no direct test. Mocking node:child_process's execFile would let you assert the base64-decode path and the swallow-on-error behavior that the rest of the fetch chain depends on.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 69d2dc49-5978-4ce0-8c89-e78456bbbc63

📥 Commits

Reviewing files that changed from the base of the PR and between 9d51732 and 0115159.

📒 Files selected for processing (21)
  • src/main/rate-limits/antigravity-local-quota.test.ts
  • src/main/rate-limits/antigravity-local-quota.ts
  • src/main/rate-limits/antigravity-oauth-keyring.test.ts
  • src/main/rate-limits/antigravity-oauth-keyring.ts
  • src/main/rate-limits/antigravity-quota-summary.test.ts
  • src/main/rate-limits/antigravity-quota-summary.ts
  • src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts
  • src/main/rate-limits/gemini-usage-fetcher.test.ts
  • src/main/rate-limits/gemini-usage-fetcher.ts
  • src/renderer/src/components/settings/AccountsPane.test.tsx
  • src/renderer/src/components/settings/AccountsPane.tsx
  • src/renderer/src/components/settings/accounts-search.ts
  • src/renderer/src/components/status-bar/tooltip.test.ts
  • src/renderer/src/components/status-bar/tooltip.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/shared/rate-limit-types.ts
  • src/shared/types.ts
👮 Files not reviewed due to content moderation or server errors (6)
  • src/renderer/src/components/status-bar/tooltip.tsx
  • src/renderer/src/components/status-bar/tooltip.test.ts
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json

Comment thread src/main/rate-limits/gemini-usage-fetcher.ts
Comment thread src/main/rate-limits/gemini-usage-fetcher.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/rate-limits/antigravity-oauth-keyring.ts (1)

174-175: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Unconditionally trim stdout to remove trailing newlines.

Command-line tools like macOS security typically append a trailing newline to their output. If parseAntigravityKeyringCredentials uses strict equality checks or prefix matching, the trailing newline could cause valid credentials to be rejected. Trimming stdout for all output types ensures the parser receives a clean value.

💡 Proposed tweak
     const keyringValue =
-      command.output === 'base64' ? Buffer.from(stdout.trim(), 'base64').toString('utf8') : stdout
+      command.output === 'base64' ? Buffer.from(stdout.trim(), 'base64').toString('utf8') : stdout.trim()

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7d453817-5955-4b7d-96d9-d3b54350fb20

📥 Commits

Reviewing files that changed from the base of the PR and between 0115159 and f93b742.

📒 Files selected for processing (7)
  • src/main/rate-limits/antigravity-local-quota.ts
  • src/main/rate-limits/antigravity-oauth-keyring-read.test.ts
  • src/main/rate-limits/antigravity-oauth-keyring.ts
  • src/main/rate-limits/antigravity-quota-summary.ts
  • src/main/rate-limits/gemini-usage-fetcher.test.ts
  • src/main/rate-limits/gemini-usage-fetcher.ts
  • src/renderer/src/components/status-bar/tooltip.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/rate-limits/antigravity-quota-summary.ts
  • src/renderer/src/components/status-bar/tooltip.tsx
  • src/main/rate-limits/antigravity-local-quota.ts

Comment on lines +96 to +101
case 'win32':
return {
command: 'powershell.exe',
args: ['-NoProfile', '-NonInteractive', '-Command', buildWindowsCredentialScript()],
output: 'base64'
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use -EncodedCommand for robust PowerShell execution.

Passing a multi-line script containing a here-string (@'...'@) directly via -Command is fragile. Depending on how the environment handles command-line newlines, it can easily lead to PowerShell syntax errors (e.g., "The string is missing the terminator").

It is a standard best practice to pass complex scripts as a Base64-encoded UTF-16LE string using -EncodedCommand. Additionally, adding -ExecutionPolicy Bypass ensures the script runs reliably even in environments with restrictive execution policies.

🛠️ Proposed fix
     case 'win32':
+      const script = buildWindowsCredentialScript()
+      const encodedCommand = Buffer.from(script, 'utf16le').toString('base64')
       return {
         command: 'powershell.exe',
-        args: ['-NoProfile', '-NonInteractive', '-Command', buildWindowsCredentialScript()],
+        args: ['-ExecutionPolicy', 'Bypass', '-NoProfile', '-NonInteractive', '-EncodedCommand', encodedCommand],
         output: 'base64'
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case 'win32':
return {
command: 'powershell.exe',
args: ['-NoProfile', '-NonInteractive', '-Command', buildWindowsCredentialScript()],
output: 'base64'
}
case 'win32':
const script = buildWindowsCredentialScript()
const encodedCommand = Buffer.from(script, 'utf16le').toString('base64')
return {
command: 'powershell.exe',
args: ['-ExecutionPolicy', 'Bypass', '-NoProfile', '-NonInteractive', '-EncodedCommand', encodedCommand],
output: 'base64'
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants