Skip to content

fix(rate-limits): read Antigravity quota from the agy CLI - #23426

Open
lurunzi wants to merge 1 commit into
stablyai:mainfrom
lurunzi:fix/antigravity-cli-quota
Open

lurunzi wants to merge 1 commit into
stablyai:mainfrom
lurunzi:fix/antigravity-cli-quota

Conversation

@lurunzi

@lurunzi lurunzi commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

ELI5

The Antigravity entry in the status bar never shows usage for personal Google accounts. Orca was borrowing the Gemini CLI's quota, and Google now refuses that request. This PR asks the Antigravity CLI (agy) for its own numbers instead, so the entry shows real usage without the Antigravity app running.

What Changed

  • Before: the Antigravity entry was a relabelled copy of the Gemini CLI quota read. For personal accounts, loadCodeAssist now returns no project and marks the free tier UNSUPPORTED_CLIENT ("migrate to Antigravity"), so the entry always says usage is unavailable. It also stayed hidden unless Gemini CLI OAuth was turned on.
  • After: Orca runs agy --print /usage --output-format json with fixed arguments, a timeout, and cancellation support. It shows both Antigravity pools, Gemini models and Claude/GPT, each with its 5-hour and weekly window and reset time. The Gemini-to-Antigravity mirror is removed. The Antigravity entry no longer depends on the Gemini OAuth toggle.
  • agy --version must report 1.1.11 or newer before /usage is run, because older versions treat /usage as a paid model prompt. A custom Antigravity command is used when it is a plain executable path. A command with extra arguments is refused rather than run as a quota query.

Why

This is not new design. It is the quota part of #21654 and #21678, which a maintainer already reviewed and chose over the language-server and keychain approaches, ported onto current main. Those PRs were merged into a long stacked branch, and that stack is still blocked by unmerged PRs such as #21653, so main still ships the Gemini mirror. Taking only the quota files lets this fix land on its own.

Compared with the other approaches:

Conflicts with current main were small: the new OpenCode Go fetch call, Cursor bucket visibility in StatusBarProviderSegment, and the OpenCode Go API-key visibility signal. The newer service-cursor-usage.test.ts needed a mock for the Antigravity fetcher.

Linked Issue

Fixes #9122. Addresses #19561, #22511, #23008, and the quota part of #19587 and #17345.

Visual Proof

No screenshot. The app could not be launched from this worktree because the native module rebuild fails on this machine. Instead, the real fetcher was run against a signed-in agy 1.2.12 on Windows 11 and returned status: ok with four buckets:

Gemini Models          Weekly  83% used   | 5h  21% used
Claude and GPT models  Weekly  15% used   | 5h   0% used

The CLI reported num_turns: 0 and total_tokens: 0 for this probe. The rendering is unchanged from #21654 and #21678; see the proof in #21678.

Testing

  • I manually tested these changes locally (live fetcher against agy 1.2.12 on Windows 11. The rendered UI was not checked, see above.)
  • Automated tests added/updated, or explained why not below

On Windows, tsc passed for both the node and web configs, oxlint was clean, and check-changed-code-quality passed with 0 findings. The vitest run over src/main/rate-limits and src/renderer/src/components/status-bar had 1070 passing and 10 failing. The same 10 tests fail on unmodified main on this machine (Codex RPC, OpenCode XDG path, Claude WSL target), so this PR adds no new failures. macOS and Linux were not run locally.

Pitfall for reviewers testing by hand on Windows: from Git Bash, MSYS rewrites /usage into a file path, and agy then runs a real, billable model turn. Test from PowerShell or cmd. Orca spawns agy directly, so it is not affected.

AI Disclosure

Ported and verified with Claude (Opus 5.5). The original implementation is by @abti-ai (#20797) and @nwparker (#21654, #21678), both credited as co-authors on the commit.

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Security: no credentials are read. The command runs with a fixed argument list and no shell, through the shared process runner.
  • SSH / remote: like fix(antigravity): read native quota only on supported CLI versions #21654, the quota is read from agy on the machine running Orca. Remote and WSL quota were not verified.
  • Performance: one --version call and one /usage call per refresh, each with a 10-second limit.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

🤖 Generated with Claude Code

Antigravity usage mirrored the Gemini CLI quota read, which Google now
rejects for individual accounts (UNSUPPORTED_CLIENT), so the status bar
never showed Antigravity usage. Read `agy --print /usage --output-format
json` instead (version-gated to agy >= 1.1.11, using the configured
executable), and show the Gemini and Claude/GPT pools with their 5h and
weekly windows.

Standalone port of the quota portion of stablyai#21654 and stablyai#21678, which are
stacked behind unmerged PRs, onto current main.

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Abhishek AI <325505575+abti-ai@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lurunzi added a commit to lurunzi/orca that referenced this pull request Sep 27, 2026
Replaces the fork's local language-server probe (superseded, stablyai#22862
closed) with the agy CLI /usage fetcher, so quota shows without the
Antigravity app running. Keeps the fork's Cursor mocks and win32 test
expectations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 1ba56dde-8997-4009-99b8-cb9ea644f3fc

📥 Commits

Reviewing files that changed from the base of the PR and between 27b823f and ed3c9c5.

📒 Files selected for processing (36)
  • src/main/rate-limits/antigravity-usage-command.test.ts
  • src/main/rate-limits/antigravity-usage-command.ts
  • src/main/rate-limits/antigravity-usage-fetcher.test.ts
  • src/main/rate-limits/antigravity-usage-fetcher.ts
  • src/main/rate-limits/antigravity-usage-mirror.test.ts
  • src/main/rate-limits/antigravity-usage-mirror.ts
  • src/main/rate-limits/rate-limit-service-test-harness.ts
  • src/main/rate-limits/service-account-target-selection.test.ts
  • src/main/rate-limits/service-antigravity-usage.test.ts
  • src/main/rate-limits/service-cursor-usage.test.ts
  • src/main/rate-limits/service-inactive-account-previews.test.ts
  • src/main/rate-limits/service-live-claude-usage.test.ts
  • src/main/rate-limits/service-minimax-usage.test.ts
  • src/main/rate-limits/service-refresh-orchestration.test.ts
  • src/main/rate-limits/service-window-activation.test.ts
  • src/main/rate-limits/service/service-configuration.ts
  • src/main/rate-limits/service/service-full-cycle-application.ts
  • src/main/rate-limits/service/service-full-cycle-preparation.ts
  • src/main/rate-limits/service/service-state.ts
  • src/main/startup/main-process-account-services.ts
  • src/renderer/src/components/status-bar/StatusBarProviderSegment.test.tsx
  • src/renderer/src/components/status-bar/StatusBarProviderSegment.tsx
  • src/renderer/src/components/status-bar/StatusBarVisibilityMenu.tsx
  • src/renderer/src/components/status-bar/UsageRosterPanel.test.tsx
  • src/renderer/src/components/status-bar/UsageRosterPanel.tsx
  • src/renderer/src/components/status-bar/antigravity-compact-metrics.tsx
  • src/renderer/src/components/status-bar/antigravity-usage-format.ts
  • src/renderer/src/components/status-bar/status-bar-agent-gating.test.ts
  • src/renderer/src/components/status-bar/status-bar-agent-gating.ts
  • src/renderer/src/components/status-bar/status-bar-provider-visibility.test.ts
  • src/renderer/src/components/status-bar/status-bar-provider-visibility.ts
  • src/renderer/src/components/status-bar/tooltip.test.ts
  • src/renderer/src/components/status-bar/tooltip.tsx
  • src/renderer/src/components/status-bar/usage-section-selection.ts
  • src/renderer/src/components/status-bar/use-status-bar-controller.ts
  • src/shared/rate-limit-types.ts
💤 Files with no reviewable changes (2)
  • src/main/rate-limits/antigravity-usage-mirror.test.ts
  • src/main/rate-limits/antigravity-usage-mirror.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Antigravity quota data now comes from the agy CLI instead of being derived from Gemini results. The rate-limit service tracks the configured Antigravity command and applies its fetch result independently. Status-bar availability accounts for Antigravity settings, and quota panels display grouped buckets with compact and verbose formats.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to ed3c9

Independent Antigravity quota fetching has no identified merge-blocking issue. The reviewed cancellation and default-command visibility paths remain intact.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to ed3c9

Quota refresh now runs the installed Antigravity CLI independently of the Gemini setting. Fixed arguments, a minimum-version check, time limits, and cancellation reduce the risk, but the behavior of the selected executable remains an important dependency. No specific security flaw was established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The newly reachable execution sink is the selected local CLI during rate-limit refresh. The observed path does not identify a remote caller, elevated execution identity, or new infrastructure authority; executable provenance and host-level effects remain outside the established scope.

Trust Boundaries and Controls

  • observed — The settings-derived command crosses into process execution only after executable-only resolution. The runner receives the program and fixed argument arrays separately; version failure stops the usage call, and aborts propagate rather than becoming successful quota results.

Resilience and Maintainability Implications

  • observed — Refresh keeps Antigravity state associated with its captured command: a changed command clears old data, and a result is not applied if the live command differs. This limits stale state after configuration changes but does not establish the trustworthiness of an arbitrary executable.

Hardening Proposals

  • proposed — If settings can be written by a less-trusted actor, define and enforce who may select the quota executable. Consider an explicit independent-polling opt-out if disabling Gemini OAuth or hiding the status-bar item is expected to stop Antigravity CLI execution.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 34 functions across 34 files. 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 clearly and concisely describes the main change: reading Antigravity quota directly from the agy CLI.
Description check ✅ Passed The description covers the required explanation, changes, rationale, linked issue, testing, security, platform impact, and checklist. It documents the missing UI verification and test failures. The Vi…
Linked Issues check ✅ Passed The changes satisfy the coding objectives in [#9122]. fetchAntigravityRateLimits queries agy --print /usage --output-format json independently, enforces the required CLI version, supports timeout …
Out of Scope Changes check ✅ Passed The changes remain within [#9122]. Command resolution, service orchestration, state handling, status-bar visibility, quota formatting, grouped display, and related tests support the independent Antigr…
  • Fix all pre-merge checks with AI

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.

@pullfrog pullfrog 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.

Important

The Antigravity quota-command guard accepts a leading env-assignment override (ACCOUNT=x agy) and then silently drops the assignment, so the quota read can use a different account than the user's terminal. A second, smaller propagation gap is inline too.

Reviewed changes

  • New agy quota fetcher (antigravity-usage-fetcher.ts) — runs agy --version gated at 1.1.11, then agy --print /usage --output-format json with a fixed argv, timeouts, cancellation and JSON parsing into grouped 5h/weekly buckets.
  • Executable selection (antigravity-usage-command.ts) — resolves the configured override to a single executable and refuses overrides that carry extra arguments.
  • Service integration — Antigravity becomes its own fetch lane; the Gemini mirror is deleted; the configured command is tracked across cycles and stale data is dropped when it changes.
  • Renderer — groups Antigravity buckets by pool with native Session/Weekly labels across the status bar, roster and tooltip, and drops the Gemini-OAuth dependency from Antigravity availability.
  • Types — RateLimitWindow/RateLimitBucket gain optional windowLabel, id, groupName, groupDescription.

I verified the parser's command.data.groups[].buckets[] shape, the remaining_fraction direction, and the 1.1.11 version gate against the vendor docs/release notes and captured payloads — all consistent. I also confirmed hasReachedAppVersion fails closed on 1.1.11-rc.1, empty, and warning: 1.2.7 output.

ℹ️ Antigravity quota is read from the machine running Orca

fetchAntigravityRateLimits shells out to the local agy, so on a remote or WSL workspace the bar reflects the local machine's Antigravity account — or unavailable when agy is absent locally even though it exists on the host. The previous Gemini mirror read locally as well, so this is not a regression, but the entry now presents itself as native Antigravity usage without naming the host. This is called out as unverified in the PR body; confirming that local-only is the intended scope would be worth a sentence.

Technical details
# Antigravity quota host scope

## Affected sites
- `src/main/rate-limits/antigravity-usage-fetcher.ts` — `resolveAntigravityUsageCommand` /
  `execFileCaptureToTermination` run on the Orca main process host, not the workspace host.
- `src/main/startup/main-process-account-services.ts:138` — resolver reads local settings.

## Open question for the human
- Is local-only Antigravity quota the intended contract for SSH/WSL workspaces, or should the
  entry be hidden/suffixed when the active workspace runs on a remote host?

ℹ️ Nitpicks

  • groupDescription is parsed (antigravity-usage-fetcher.ts:70) and threaded through RateLimitBucket, getWindowSections and UsageSection, but no renderer reads it — the parser could drop it until a surface needs it.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

platform: NodeJS.Platform = process.platform
): { ok: true; command: string } | { ok: false; error: string } {
const plan = planAgentBinary('agy', override, platform === 'win32' ? 'literal' : 'escape')
if (!plan.ok || plan.prefixArgs.length > 0) {

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.

planAgentBinary lifts a leading NAME=value token into plan.env, so an override like ACCOUNT=x agy tokenizes to env={ACCOUNT:x} with an empty prefixArgs and passes this guard; the env is then silently discarded when /usage is spawned, so the quota read can hit a different account than the user's terminal. Reject when plan.env is set, or thread it into the fetch.

The test's env ACCOUNT=test agy case is rejected only because env becomes the binary, so it does not cover this.

Technical details
# Env-assignment override escapes the executable-only guard

## Affected sites
- `src/main/rate-limits/antigravity-usage-command.ts:10` — only checks `plan.prefixArgs`, not `plan.env`.
- `src/main/rate-limits/antigravity-usage-fetcher.ts:192` — spawns `AGY_USAGE_ARGS` with no env override.

## Required outcome
- An override that carries environment assignments must not be silently used without them; either refuse it (consistent with the "only the executable path" error) or pass `plan.env` through to the spawn.
Suggested change
if (!plan.ok || plan.prefixArgs.length > 0) {
if (!plan.ok || plan.prefixArgs.length > 0 || plan.env) {

detectedAgentIds: TuiAgent[] | null,
settings?: Pick<GlobalSettings, 'agentCmdOverrides'> | null
): boolean {
if (id === 'antigravity' && hasExplicitTuiLaunchCommand(settings, 'antigravity')) {

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.

The new settings-aware branch is reached by StatusBarVisibilityMenu and use-status-bar-controller, but useAvailableStatusBarToggles (use-available-status-bar-toggles.ts:11) still calls with two arguments. As a result the Appearance pane hides the Antigravity toggle when agy is not on PATH even though a custom launch command is configured, while the status-bar menu shows it. Threading settings through that hook would keep availability consistent.

This branch has not been deployed

No deployments
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.

[Bug]: Antigravity usage is a copy of the Gemini quota — wrong pools, and refresh breaks entirely without a local Gemini CLI install

1 participant