Repository navigation
fix(antigravity): fetch usage from local runtime quota RPC - #23382
yuta27jojo-rgb wants to merge 2 commits into
Conversation
…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.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. 📝 WalkthroughWalkthroughAntigravity 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 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 ReviewSecurity architecture risk: 🔵 Low · up to 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
Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 22cff81c-191d-4fad-ac93-e9008cb5366a
📒 Files selected for processing (24)
src/main/rate-limits/antigravity-fetcher.test.tssrc/main/rate-limits/antigravity-fetcher.tssrc/main/rate-limits/antigravity-process-discovery.test.tssrc/main/rate-limits/antigravity-process-discovery.tssrc/main/rate-limits/antigravity-usage-mirror.test.tssrc/main/rate-limits/antigravity-usage-mirror.tssrc/main/rate-limits/rate-limit-service-test-harness.tssrc/main/rate-limits/service-account-target-selection.test.tssrc/main/rate-limits/service-antigravity-usage.test.tssrc/main/rate-limits/service-cursor-usage.test.tssrc/main/rate-limits/service-inactive-account-previews.test.tssrc/main/rate-limits/service-live-claude-usage.test.tssrc/main/rate-limits/service-minimax-usage.test.tssrc/main/rate-limits/service-refresh-orchestration.test.tssrc/main/rate-limits/service-window-activation.test.tssrc/main/rate-limits/service/service-full-cycle-application.tssrc/main/rate-limits/service/service-full-cycle-preparation.tssrc/renderer/src/components/status-bar/StatusBarProviderSegment.tsxsrc/renderer/src/components/status-bar/status-bar-provider-visibility.test.tssrc/renderer/src/components/status-bar/status-bar-provider-visibility.tssrc/renderer/src/components/status-bar/usage-provider-settings-target.test.tssrc/renderer/src/components/status-bar/usage-provider-settings-target.tssrc/renderer/src/components/status-bar/use-status-bar-controller.tssrc/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.
| function isAntigravityCommandLine(commandLine: string): boolean { | ||
| return ( | ||
| /--app_data_dir[=\s]+antigravity\b/i.test(commandLine) || | ||
| /[\\/]antigravity[\\/]/i.test(commandLine) | ||
| ) | ||
| } |
There was a problem hiding this comment.
🎯 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.tsRepository: 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 -120Repository: 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.
There was a problem hiding this comment.
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 thelanguage_serverprocess, extracts--csrf_token, and enumerates its listening ports on win32 (PowerShell) and unix (pgrep/lsof). - Quota fetcher (
antigravity-fetcher.ts) — POSTsRetrieveUserQuotaSummaryover loopback TLS with the CSRF header and parses four named buckets (Gemini/Claude-GPT × weekly/5h). - Mirror removal — deletes
antigravity-usage-mirror.ts/.test.tsand wires Antigravity as an independent sibling fetch (prepare/apply/stale-policy/failure-streak) instead of deriving from the Gemini read. - Renderer —
isAntigravityUsageBucketadded to status-bar visibility; the Gemini settings deep-link is removed;antigravityUsageConfiguredis now a standalone durable signal. - Tests — new fetcher and discovery unit tests; service tests gain an
antigravity-fetchermock; 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;ProviderRateLimitshas 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—runShellCommandis Windows-only but named generically;runWindowsShellCommandwould match its actual scope.
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' |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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 || |
There was a problem hiding this comment.
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.|
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 whyThe adapter reads agy's existing consumer OAuth credential from macOS Keychain, calls 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 Verification
Scope and limitationsThis 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. |
|
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. |

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
RetrieveUserQuotaSummaryRPC 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)):Implementation
language_serverruntime 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.exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary— the same RPC Antigravity's own Settings > Models & Usage panel uses.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.runProcesschokepoint (src/shared/child-process/run-process.ts) rather than importingnode:child_processdirectly, so it inheritswindowsHideand the project's process-spawning invariants.127.0.0.1.Verification
pnpm run typecheck— passing.child-process-import-boundary/windows-console-visibilityratchets — passing (no new directchild_processimports, no new unhidden spawns).RetrieveUserQuotaSummarywas 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):Limitations
language_serverruntime) 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.RetrieveUserQuotaSummaryis an internal Antigravity RPC, not a documented public API, and could change in a future Antigravity release.Related issues
Fixes #9122
Fixes #19561