Skip to content

fix(antigravity): fetch usage from local runtime quota RPC - #23382

Closed
yuta27jojo-rgb wants to merge 2 commits into
stablyai:mainfrom
yuta27jojo-rgb:fix/antigravity-runtime-usage
Closed

yuta27jojo-rgb wants to merge 2 commits into
stablyai:mainfrom
yuta27jojo-rgb:fix/antigravity-runtime-usage

Conversation

@yuta27jojo-rgb

Copy link
Copy Markdown

Summary

Antigravity usage was previously derived from Gemini CLI rate limits rather than Antigravity's own quota source. This change reads Antigravity's actual quota data from the local language server's RetrieveUserQuotaSummary RPC and exposes the four quota windows Antigravity's own Settings > Models & Usage panel shows.

Root cause

Antigravity did not have an independent rate-limit fetcher. Its provider limits were created by relabeling Gemini CLI limits as Antigravity (deriveAntigravityRateLimits(gemini)):

  • A successful Gemini read republished Gemini's own model-bucket numbers under the Antigravity provider.
  • A Gemini read failure (missing Gemini CLI, expired/unsupported Gemini credentials, etc.) surfaced as "Usage unavailable" for Antigravity even though Antigravity itself was never queried.

Implementation

  • Discover the local Antigravity language_server runtime process (language_server.exe / language_server_windows_x64.exe / language_server_macos* / language_server_linux*) via its command line (--csrf_token, --app_data_dir antigravity) and locate its listening Connect-RPC port.
  • Call exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary — the same RPC Antigravity's own Settings > Models & Usage panel uses.
  • Parse the response into:
    • Gemini Models / Weekly
    • Gemini Models / 5h
    • Claude and GPT models / Weekly
    • Claude and GPT models / 5h
  • Normalize these into the existing ProviderRateLimits.buckets / RateLimitBucket[] mechanism already used for Gemini's per-model buckets and Cursor's plan pools, so no type or UI-shape changes were needed to show all four windows.
  • Wire Antigravity as an independent sibling fetch (same pattern as Grok/Cursor), fully decoupled from the Gemini fetcher and its OAuth opt-in setting.
  • Process discovery goes through the existing shared runProcess chokepoint (src/shared/child-process/run-process.ts) rather than importing node:child_process directly, so it inherits windowsHide and the project's process-spawning invariants.
  • No credentials or CSRF tokens are logged; the CSRF token is only read from the already-local process's own command line and sent as a request header to 127.0.0.1.

Verification

  • New unit tests (parser/fetcher, process discovery, provider isolation, status-bar visibility) — all passing.
  • pnpm run typecheck — passing.
  • child-process-import-boundary / windows-console-visibility ratchets — passing (no new direct child_process imports, no new unhidden spawns).
  • Windows live verification: RetrieveUserQuotaSummary was called directly against a running Antigravity instance and compared to Orca's status-bar output at the same time. All four buckets matched after percentage rounding. Example from that verification run (not hard-coded expected values — just what was observed at that moment):
    • Gemini Weekly: 93.23% remaining -> 7% used in Orca
    • Gemini 5h: 99.27% remaining -> 1% used in Orca
    • Claude/GPT Weekly: 100% remaining -> 0% used in Orca
    • Claude/GPT 5h: 100% remaining -> 0% used in Orca
  • Confirmed in the same session that Codex, Claude, and Cursor usage kept rendering normally alongside the new Antigravity segment, and that Antigravity's numbers no longer match/mirror Gemini's.

Limitations

  • The Antigravity IDE (its background language_server runtime) must be running on the same machine; there is no persisted-credential fallback for reading quota while it is closed, since Antigravity does not expose one.
  • macOS/Linux process discovery is implemented and unit-tested but was not live-verified in this session (Windows only).
  • RetrieveUserQuotaSummary is an internal Antigravity RPC, not a documented public API, and could change in a future Antigravity release.

Related issues

Fixes #9122
Fixes #19561

…irroring Gemini

Antigravity's rate-limit provider never queried Antigravity itself; it copied
whatever the Gemini CLI fetcher returned onto provider:'antigravity'
(deriveAntigravityRateLimits). A successful Gemini read republished Gemini's
numbers as Antigravity's (stablyai#9122), and a Gemini failure surfaced as an
Antigravity "usage unavailable" even though Antigravity was never queried
(stablyai#19561).

Replace it with a real fetcher that talks to the same source Antigravity's own
Settings > Models & Usage panel uses: the local `language_server` runtime's
Connect-RPC `RetrieveUserQuotaSummary` call
(exa.language_server_pb.LanguageServerService), discovered via its process
command line (--csrf_token, --app_data_dir antigravity) and listening port.
This yields the real four windows - Gemini/Claude+GPT x weekly/5h - reusing
the existing ProviderRateLimits.buckets mechanism already used for Gemini's
per-model buckets and Cursor's plan pools, so no type or UI-shape changes were
needed to show all four.

Wired as an independent sibling fetch (matching Grok/Cursor), fully decoupled
from the Gemini fetcher and its OAuth opt-in setting.

Verified against a running Antigravity instance: the fetcher's own numbers
matched a direct call to the same RPC, and the values rendered correctly in
Orca's status bar in a dev build.
The process-discovery test fixture had a CSRF token captured from a live
local Antigravity session (already invalid - it dies with that process).
Replace it with an obviously-fake placeholder before this goes upstream.
@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.

📝 Walkthrough

Walkthrough

Antigravity usage now comes from quota summaries fetched from its local runtime, rather than from Gemini quota results. The rate-limit service handles Antigravity as a separate provider. Status-bar visibility recognizes its usage buckets, and a checked Antigravity setting no longer depends on Gemini OAuth or links to Gemini’s Accounts section.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🔵 Low · up to 7751e

Usage could show another local runtime’s quota in a narrow process-discovery case. Tighten runtime selection before merging if that case matters for supported installations.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 7751e

A locally running lookalike process could supply false Antigravity usage values. The apparent impact is limited to usage information shown by the app; the reviewed path does not perform privileged runtime actions.

Retained concerns

  • Low · security · inferred: The new quota source can be impersonated by a local process that supplies matching command-line metadata, its own token, and a matching RPC response. This permits falsified Antigravity usage information, not a demonstrated remote or privileged action.
Security review details

Security Blast Radius

  • inferred — The demonstrated impersonation scope is a local process supplying Antigravity quota data for this app's provider state; the inspected caller neither sends Gemini credentials nor invokes a runtime mutation RPC.

Security Findings and Attack Paths

  • inferred — A local lookalike process can satisfy the discovery predicates, provide its own token and listener, and return recognizable but false quotas. Process metadata and the token obtained from it do not independently authenticate the runtime.

Trust Boundaries and Controls

  • observed — Positive PID checks, PID-associated listener discovery, loopback-only transport, timeout and status checks, and bucket validation constrain accidental matches and malformed replies. They do not independently establish the selected process's identity.

Resilience and Maintainability Implications

  • observed — Sibling-result settlement preserves the Antigravity provider key on rejection, and the application uses its own prior snapshot rather than Gemini's when applying stale policy.

Hardening Proposals

  • proposed — Bind runtime selection to an independently trusted executable or installation identity where feasible, rather than treating a process's name, arguments, and self-supplied token as sufficient identity evidence.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.30% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 33 functions across 21 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: fetching Antigravity usage from its local runtime quota RPC.
Description check ✅ Passed The description is detailed and covers the user impact, root cause, implementation, linked issues, testing, platform coverage, security considerations, and limitations. It does not reproduce every tem…
Linked Issues check ✅ Passed The PR meets the coding requirements in [#9122] and [#19561]. It removes the Gemini-to-Antigravity mirror and adds an independent fetchAntigravityRateLimits path. The path discovers the local Antigr…
Out of Scope Changes check ✅ Passed The changed files support the linked objectives. Fetching, process discovery, quota parsing, service integration, status-bar visibility, provider settings, and related tests implement or verify the in…
  • 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.

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 22cff81c-191d-4fad-ac93-e9008cb5366a

📥 Commits

Reviewing files that changed from the base of the PR and between 0697014 and 7751e6c.

📒 Files selected for processing (24)
  • src/main/rate-limits/antigravity-fetcher.test.ts
  • src/main/rate-limits/antigravity-fetcher.ts
  • src/main/rate-limits/antigravity-process-discovery.test.ts
  • src/main/rate-limits/antigravity-process-discovery.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-full-cycle-application.ts
  • src/main/rate-limits/service/service-full-cycle-preparation.ts
  • src/renderer/src/components/status-bar/StatusBarProviderSegment.tsx
  • 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/usage-provider-settings-target.test.ts
  • src/renderer/src/components/status-bar/usage-provider-settings-target.ts
  • src/renderer/src/components/status-bar/use-status-bar-controller.ts
  • src/shared/antigravity-usage-buckets.ts
💤 Files with no reviewable changes (3)
  • src/renderer/src/components/status-bar/use-status-bar-controller.ts
  • 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.

Comment on lines +15 to +20
function isAntigravityCommandLine(commandLine: string): boolean {
return (
/--app_data_dir[=\s]+antigravity\b/i.test(commandLine) ||
/[\\/]antigravity[\\/]/i.test(commandLine)
)
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '1,205p' src/main/rate-limits/antigravity-process-discovery.ts
rg -n 'app_data_dir|antigravity[\\/]|language_server' src/main/rate-limits/antigravity-process-discovery.test.ts

Repository: stablyai/orca

Length of output: 7230


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- discovery tests ---'
cat -n src/main/rate-limits/antigravity-process-discovery.test.ts | sed -n '1,210p'
printf '%s\n' '--- relevant repository references ---'
rg -n -S --glob '!node_modules' --glob '!dist' --glob '!build' \
  'discoverAntigravityRuntime|AntigravityProcessInfo|app_data_dir|csrf_token|language_server_(windows|macos|linux)|override_ide_name|subclient_type|quota' \
  src test tests fixtures README.md docs 2>/dev/null || true
printf '%s\n' '--- candidate fixture paths ---'
git ls-files | rg -i 'antigravity|process-discovery|fixture|contract' | head -120

Repository: stablyai/orca

Length of output: 40886


🏁 Script executed:

cat -n src/main/rate-limits/antigravity-process-discovery.test.ts | sed -n '1,210p'
printf '\n--- direct consumers and command-line contracts ---\n'
rg -n -S 'discoverAntigravityRuntime|app_data_dir|csrf_token|language_server|override_ide_name|subclient_type' src
printf '\n--- relevant tracked files ---\n'
git ls-files | rg -i 'antigravity|process-discovery|fixture|contract'

Repository: stablyai/orca

Length of output: 40717


🏁 Script executed:

printf '%s\n' '--- Antigravity fetcher binding ---'
cat -n src/main/rate-limits/antigravity-fetcher.ts | sed -n '1,240p'
printf '%s\n' '--- fetcher request and response tests ---'
cat -n src/main/rate-limits/antigravity-fetcher.test.ts | sed -n '235,315p'
printf '%s\n' '--- readiness and compatibility evidence ---'
cat -n docs/reference/antigravity-readiness-evidence.md | sed -n '1,240p'
cat -n src/renderer/src/runtime/antigravity-generation-compatibility.ts | sed -n '1,220p'

Repository: stablyai/orca

Length of output: 38004


Prefer explicit Antigravity identity over the path fallback.

findWindowsProcess and findUnixProcess accept the first language_server candidate whose command line contains an antigravity path segment, even when --app_data_dir names another IDE. If that candidate has a CSRF token and a listening port, fetchAntigravityRateLimits sends RetrieveUserQuotaSummary to it and labels the returned buckets as Antigravity usage. This can misattribute a local runtime's quota.

Rank --app_data_dir antigravity matches before path-only matches. Keep the path check as a fallback only if supported Antigravity command lines can omit --app_data_dir; otherwise remove it. Preference prevents a path-only candidate from winning when the exact Antigravity process is also running, but it does not eliminate the collision when no exact candidate exists.

@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

Windows discovery bypasses the shared process-table reader and forks powershell.exe per poll, which AGENTS.md and docs/reference/windows-process-enumeration.md explicitly forbid. Two smaller findings are inline: a missing response-error handler in the fetcher and a setup-CTA regression from treating the default-on Antigravity item as configured usage.

Reviewed changes

  • Local runtime discovery (antigravity-process-discovery.ts) — locates the language_server process, extracts --csrf_token, and enumerates its listening ports on win32 (PowerShell) and unix (pgrep/lsof).
  • Quota fetcher (antigravity-fetcher.ts) — POSTs RetrieveUserQuotaSummary over loopback TLS with the CSRF header and parses four named buckets (Gemini/Claude-GPT × weekly/5h).
  • Mirror removal — deletes antigravity-usage-mirror.ts/.test.ts and wires Antigravity as an independent sibling fetch (prepare/apply/stale-policy/failure-streak) instead of deriving from the Gemini read.
  • Renderer — isAntigravityUsageBucket added to status-bar visibility; the Gemini settings deep-link is removed; antigravityUsageConfigured is now a standalone durable signal.
  • Tests — new fetcher and discovery unit tests; service tests gain an antigravity-fetcher mock; visibility tests updated for the new semantics.

The provider-isolation fix itself is well targeted — dropping deriveAntigravityRateLimits is the right root-cause change, and the deleted mirror file leaves no dangling references.

ℹ️ Nitpicks

  • antigravity-fetcher.test.ts:350 — expect(Object.keys(result)).not.toContain('geminiProjectId') is vacuous; ProviderRateLimits has no such field, so the assertion passes no matter how coupled the fetcher becomes. Assert something the code could actually violate.
  • antigravity-process-discovery.ts:30 — runShellCommand is Windows-only but named generically; runWindowsShellCommand would match its actual scope.

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

async function findWindowsProcess(): Promise<{ pid: number; csrfToken: string } | null> {
// Why: -Filter is a static WQL string with no interpolated data.
const stdout = await runShellCommand(
'Get-CimInstance Win32_Process -Filter "Name LIKE \'language_server%\'" | Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress'

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.

This reads the Windows process table by forking powershell.exe for Get-CimInstance Win32_Process. That is the exact pattern AGENTS.md and docs/reference/windows-process-enumeration.md forbid — src/main/windows/windows-process-table.ts is the only module allowed to read the table — and because discovery runs on the rate-limit poll it re-adds the per-poll CIM fan-out (and the EDR spawn signal) that module was built to remove.

Technical details
# Windows process discovery must go through the shared process-table reader

## Affected sites
- `src/main/rate-limits/antigravity-process-discovery.ts:63-67` — `Get-CimInstance Win32_Process` scan.
- `src/main/rate-limits/antigravity-process-discovery.ts:103-124` — per-pid `Get-NetTCPConnection` port scan.
- `src/main/rate-limits/antigravity-process-discovery.ts:34-38` — spawns bare `powershell.exe` rather than `windowsPowerShellPath()`.

## Required outcome
- Windows process/command-line discovery reads through `readWindowsProcessTable()` (carries `command`) instead of a fresh `powershell.exe` CIM query.
- Any remaining PowerShell spawn uses `windowsPowerShellPath()` with the `pwsh.exe` fallback, as `src/relay/windows-port-scan.ts:194-229` does.

## Suggested approach
- `readWindowsProcessTable()` already returns `{ pid, name, command }`; select the row whose `name` starts with `language_server` and whose `command` passes `isAntigravityCommandLine`, then extract the CSRF token — no `-Filter` string or JSON plumbing needed.
- For listening ports, reuse the existing listener enumeration (`src/main/ports/local-workspace-platform-port-scanner.ts` / `scanPlatformListeningPorts`) rather than a per-pid `Get-NetTCPConnection`.

## Open questions for the human
- If neither the native addon nor the CIM fallback can attribute a listening port to the pid cheaply, is a per-pid `Get-NetTCPConnection` acceptable, or should discovery degrade to the process's `--https_server_port` when non-zero?

},
(res) => {
const chunks: Buffer[] = []
res.on('data', (chunk: Buffer) => chunks.push(chunk))

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.

res has no 'error' listener. Once the response has started, a socket error is emitted on the IncomingMessage, not the request, so req.on('error', reject) does not cover it and Node raises an unhandled 'error' in the main process. The house transport pattern attaches one — see src/main/observability/diagnostic-upload-http.ts:141-146.

Technical details
# Handle response-stream errors in postConnectRpc

## Affected sites
- `src/main/rate-limits/antigravity-fetcher.ts:62-78` — the response callback registers `data`/`end` only.

## Required outcome
- A mid-response socket error rejects the promise instead of escaping as an unhandled `'error'` event in the main process.

## Suggested approach
- Add `res.on('error', reject)` and a `settled` guard so an error arriving after `end` cannot reject an already-resolved promise. `diagnostic-upload-http.ts` is the reference implementation.

settings?.opencodeGoApiKeyConfigured === true ||
// Antigravity's durable signal requires geminiCliOAuthEnabled, so it is
// already covered by the gemini term above.
settings?.antigravityUsageConfigured === true ||

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.

antigravity is in DEFAULT_STATUS_BAR_ITEMS, so antigravityUsageConfigured is true for anyone whose Antigravity CLI is detected on PATH — whether or not they configured anything. Adding it to hasUsageProviderSettings flips isUsageEmptyState to false for a fresh profile and hides the setup CTA; the removed test asserted exactly this case should still show it, and the snapshot stays unavailable unless the IDE is running.

Technical details
# Antigravity's default-on item now suppresses the setup CTA

## Affected sites
- `src/renderer/src/components/status-bar/status-bar-provider-visibility.ts:83` — `antigravityUsageConfigured` added to `hasUsageProviderSettings`.
- `src/renderer/src/components/status-bar/status-bar-provider-visibility.test.ts:518-537` — expectation flipped to `false`.
- `src/renderer/src/components/status-bar/use-status-bar-controller.ts:106-108` — the flag derives from the default-on item plus PATH detection, not from any stored credential.
- `src/shared/status-bar-defaults.ts:3-16` — `antigravity` is in the default item list.

## Required outcome
- The setup CTA remains visible for a profile that has configured no usage provider, even when Antigravity merely happens to be installed.

## Open questions for the human
- Is a checked default-on Antigravity item intended to count as "usage configured" on its own? If so, the prior rationale (a default-on item must not hide the teaching CTA) should be retired explicitly rather than dropped with the test.

@SC-Kliff

SC-Kliff commented Oct 2, 2026

Copy link
Copy Markdown

I tested a complementary approach for people using agy CLI without the Antigravity desktop IDE running. In Orca 1.4.217, Antigravity usage previously showed "Usage unavailable"; a local proof-of-concept patch now retrieves and displays its own quota in the usage UI.

What changed and why

The adapter reads agy's existing consumer OAuth credential from macOS Keychain, calls loadCodeAssist with Antigravity client metadata, then calls retrieveUserQuotaSummary on daily-cloudcode-pa.googleapis.com. It does not depend on Gemini CLI credentials or a running Antigravity language server.

It keeps Gemini and Claude/GPT pools separate, parses weekly and 5-hour buckets and their reset times, and caches results for 30 seconds. When credentials need refreshing, it delegates that to agy models rather than implementing a separate OAuth refresh flow. This could complement the local-runtime approach in this PR when the desktop IDE is closed.

Verification

  • Tested with consumer authentication on macOS Apple Silicon, Orca 1.4.217.
  • An actual authenticated quota-summary request succeeded.
  • Three parser tests passed.
  • The patched app launched successfully, and the user confirmed the quota is visible in the actual Orca usage UI.
  • Weekly buckets were verified live. Support for 5-hour buckets is implemented, but live 5-hour quota has not been verified.

Scope and limitations

This is a local proof of concept, not an upstream-ready source patch. I have not run the repository's full lint/typecheck/test/build checks or attached before/after screenshots.

The current implementation is macOS-specific and uses an internal Google endpoint that may change. Linux, Windows, other authentication methods, SSH execution-host behavior, and mobile/backward compatibility remain unverified. An upstream implementation would need runtime platform guards, the shared process wrapper, execution-host-aware credential access, and regression tests. Credentials and raw API response bodies are excluded from error messages; the 30-second cache limits repeated requests.

This proof of concept was developed with OpenAI Codex assistance.

Would a CLI-authenticated quota fallback fit the direction of this PR? I can contribute the adapter and parser tests if useful.

Related: #19561.

@nwparker

nwparker commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Closing because the reported root cause—relabeling Gemini CLI usage as Antigravity—is already addressed on main by #24593 (merged). Antigravity now uses its own agy quota fetcher, with version gating for free quota reads and independent visibility without Gemini OAuth. See #24593 for the shipped fix and verification. This does not implement the local RetrieveUserQuotaSummary RPC proposed here. Adopting that alternate source would require a new narrow proposal preserving the working CLI-only path and verified platform discovery; the current patch requires a running IDE, has a macOS process-discovery mismatch, and adds repeated Windows PowerShell polling. The current independent-fetcher tests passed in a 94-test run. Thank you for investigating the wrong-provider readings.

@nwparker nwparker closed this Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants