Repository navigation
Conversation
Read the agy session on disk, refresh its OAuth token from the CLI binary, and fetch daily Cloud Code quota so Accounts and the status bar stop treating Antigravity as a Gemini OAuth mirror. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Important
antigravity-cli-oauth-extractor.ts imports node:child_process directly, which will fail the child-process-import-boundary ratchet test and block CI. Details inline.
Reviewed changes
- Antigravity CLI OAuth extraction — new scanner that resolves the
agybinary and extractsGOCSPX-secrets and Google client IDs, then tries every id/secret pair to refresh a stale token. - Independent quota fetch — reads the
agysession file, refreshes via the embedded OAuth client, and parsesretrieveUserQuotaSummaryfrom the daily Cloud Code host (withUser-Agent: antigravity). - Rate-limit service wiring — drops the Gemini mirror in favor of a first-class
fetchAntigravityRateLimits()in the fetch cycle, plus anantigravityAuthConfiguredflag. - Accounts UI + status bar — new
AntigravityAccountsSection, search entries, and status-bar visibility now driven byantigravityAuthConfiguredinstead of a status-bar-item + PATH detection signal.
ℹ️ Nitpicks
AntigravityAccountsSection.tsxuses semantic i18n keys (auto.components.settings.AntigravityAccountsSection.title, …) while every sibling section (e.g.GrokAccountsSection) uses the hash-key convention (…GrokAccountsSection.a1b2c3d4e5). Not CI-breaking, but it diverges from the established pattern and the extraction tooling is built around the hash keys.RateLimitBucketinsrc/shared/rate-limit-types.tsis still documented as "Named per-model buckets (Gemini only)", but Antigravity now populatesbucketstoo.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
| @@ -0,0 +1,178 @@ | |||
| import { execFile } from 'node:child_process' | |||
There was a problem hiding this comment.
Direct node:child_process import. src/shared/child-process/child-process-import-boundary.test.ts ratchets every direct importer: this file is not in __fixtures__/child-process-import-allowlist.txt, so the has no direct importer outside the allowlist and holds the offender count at the pin assertions both fail in CI. The existing gemini-cli-oauth-extractor.ts is grandfathered into the allowlist; a new sibling is not.
Technical details
# Route the agy binary lookup through the child-process chokepoint
## Affected sites
- src/main/rate-limits/antigravity-cli-oauth-extractor.ts:1 — `import { execFile } from 'node:child_process'` + `promisify(execFile)`
- src/shared/child-process/child-process-import-boundary.test.ts — the ratchet that fails on this import
## Required outcome
- The `which`/`where` probe in `resolveAgyBinary` goes through `runProcess` from `src/shared/child-process/run-process.ts`, which already pins `windowsHide`, `.cmd` resolution, and arg quoting. `runProcess` resolves with `{ code, stdout, stderr, ... }` and never rejects on a non-zero exit (the `which` failure case), so the existing `try/catch` around the lookup can become a plain `code !== 0` / empty-`stdout` check.
## Suggested approach (optional)
- Replace `execFileAsync(lookup, args, { encoding: 'utf-8', windowsHide: true })` with `await runProcess({ file: lookup, args })` and read `result.stdout` (a string). Note `resolveSpawn` encodes `.cmd` args, so keep the win32 `where.exe` path as-is — it is an exe, not a `.cmd`.| home = homedir() | ||
| ): Promise<void> { | ||
| const tokenPath = getAntigravityOAuthTokenPath(home) | ||
| const payload: AntigravityOAuthFile = { |
There was a problem hiding this comment.
saveAntigravityCredentials rebuilds the token file from only the fields Orca models (auth_method, token.{access_token, refresh_token, token_type, expiry}). If the real ~/.gemini/antigravity-cli/antigravity-oauth-token carries anything else — id_token, scope, client_id, or other fields the CLI relies on — a refresh silently strips them and can corrupt the user's agy session. The test only asserts auth_method survives, so this would go unnoticed. Please confirm the agy token schema, or merge the refreshed fields into the existing parsed document (preserving unknown keys) rather than constructing a fresh payload.
📝 WalkthroughWalkthroughAdds independent Antigravity CLI authentication and quota support. The change reads CLI OAuth sessions, discovers refresh clients from the Priority: ➖ Normal — Prioritize the Antigravity CLI integration because it broadly adds account detection, token refresh, quota fetching, IPC, and Accounts/status-bar support across the product. Severity of issue fixed: Medium Merge Risk: 🟡 Moderate · up to The change can expose refreshed credentials to other local users in some environments and can incorrectly report Antigravity quota as unavailable. These issues should be addressed before merge. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 57 functions across 46 files. (1 skipped: 1 unsupported.)
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: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 6b7404d7-e662-4f65-b99b-7ee445e83f00
📒 Files selected for processing (49)
src/main/antigravity-accounts/status.test.tssrc/main/antigravity-accounts/status.tssrc/main/ipc/antigravity-accounts.tssrc/main/ipc/register-core-handlers/register-core-handlers.test.tssrc/main/ipc/register-core-handlers/register-core-handlers.tssrc/main/rate-limits/antigravity-cli-oauth-extractor.test.tssrc/main/rate-limits/antigravity-cli-oauth-extractor.tssrc/main/rate-limits/antigravity-oauth-sources.test.tssrc/main/rate-limits/antigravity-oauth-sources.tssrc/main/rate-limits/antigravity-quota-summary.test.tssrc/main/rate-limits/antigravity-quota-summary.tssrc/main/rate-limits/antigravity-usage-fetcher.test.tssrc/main/rate-limits/antigravity-usage-fetcher.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-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-configuration.tssrc/main/rate-limits/service/service-full-cycle-application.tssrc/main/rate-limits/service/service-full-cycle-preparation.tssrc/main/rate-limits/service/service-state.tssrc/preload/api-types.tssrc/preload/api/agent-account-api.tssrc/preload/api/antigravity-accounts-bridge.tssrc/preload/index.tssrc/renderer/src/components/settings/AccountsPane.test.tsxsrc/renderer/src/components/settings/AccountsPane.tsxsrc/renderer/src/components/settings/AntigravityAccountsSection.test.tsxsrc/renderer/src/components/settings/AntigravityAccountsSection.tsxsrc/renderer/src/components/settings/accounts-search.test.tssrc/renderer/src/components/settings/accounts-search.tssrc/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/renderer/src/i18n/locales/en.jsonsrc/renderer/src/web/preload-api/web-agent-accounts-api.tssrc/renderer/src/web/web-preload-api-composition.test.tssrc/renderer/src/web/web-preload-api.tssrc/shared/rate-limit-state-factory.tssrc/shared/rate-limit-types.test.tssrc/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: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
| } | ||
| } | ||
| const tmpPath = `${tokenPath}.${process.pid}.tmp` | ||
| await writeFile(tmpPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8') |
There was a problem hiding this comment.
🔒 Security & Privacy | 🛡️ Analyzed with Security Review | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
node --version
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT
(
umask 000
TMP_DIR="$tmp_dir" node <<'NODE'
const { rename, stat, writeFile } = require('node:fs/promises')
const path = require('node:path')
async function main() {
const temporary = path.join(process.env.TMP_DIR, 'antigravity-oauth-token.1.tmp')
const target = path.join(process.env.TMP_DIR, 'antigravity-oauth-token')
await writeFile(temporary, '{"token":{"refresh_token":"secret"}}\n', 'utf8')
await rename(temporary, target)
const mode = (await stat(target)).mode & 0o777
console.log(`resulting mode: ${mode.toString(8)}`)
if ((mode & 0o077) !== 0) process.exit(1)
}
main()
NODE
)Repository: stablyai/orca
Length of output: 180
Sensitive Data Exposure (CWE-522): Insufficiently Protected Credentials
Reachability: Internal · Exploitability: Moderate
Restrict permissions on the persisted OAuth token file.
writeFile can create tmpPath with mode 0o666 before the process umask. rename preserves that mode. Create the temporary file with mode 0o600.
Proposed fix
- await writeFile(tmpPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8')
+ await writeFile(tmpPath, `${JSON.stringify(payload, null, 2)}\n`, {
+ encoding: 'utf-8',
+ mode: 0o600
+ })📝 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.
| await writeFile(tmpPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8') | |
| await writeFile(tmpPath, `${JSON.stringify(payload, null, 2)}\n`, { | |
| encoding: 'utf-8', | |
| mode: 0o600 | |
| }) |
| async function fetchQuotaSummary(accessToken: string, projectId: string | null): Promise<Response> { | ||
| const body = JSON.stringify(projectId ? { project: projectId } : {}) | ||
| let last: Response | undefined | ||
| for (const url of QUOTA_SUMMARY_URLS) { | ||
| const response = await net.fetch(url, { | ||
| method: 'POST', | ||
| headers: quotaHeaders(accessToken), | ||
| body, | ||
| signal: AbortSignal.timeout(API_TIMEOUT_MS) | ||
| }) | ||
| last = response | ||
| if (response.ok || response.status === 401 || response.status < 500) { | ||
| return response | ||
| } | ||
| } | ||
| return last! | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Continue after a 404 from the primary quota endpoint.
response.status < 500 matches 404, so fetchQuotaSummary stops before trying the sandbox compatibility endpoint. Continue for 404 responses while preserving terminal 401/403 authentication and license responses.
|
Review of head I compiled the candidate's actual
ELI5: refreshing the usage card must not make agy's login file less private or undo an account switch the user just made. A direct credential-writing implementation needs private temporary-file permissions, platform-appropriate access handling, preservation of provider fields, and a freshness/identity check that does not overwrite a newer login. Prefer avoiding ownership of agy's refresh file for quota: #21654 already reads the supported native This machine currently uses API-key authentication and has no token file at the candidate's expected location. Native |
|
Review update: this PR is superseded by #21784 for native macOS account discovery and switching. The older implementation assumes a plain Antigravity token file and extracts OAuth client secrets from the agy binary. Current installed agy evidence shows the native macOS credential is stored in the OS Keychain as a Scoped verdict: do not merge this head. Keep the PR open only if the author wants to rebase the quota work onto the native credential backend; otherwise it should be closed as superseded after human confirmation. |
|
Superseded after #24691 and #24593 merged. Thanks @artile: the signed-in Accounts card, quota/error visibility and independence from Gemini are preserved and explicitly credited in the replacements. The original credential-rewriting approach was not merged. Broader runtime/platform acceptance remains in #19587, and persistent multi-account work remains in #17345; #23761 stays open for its separate OpenCode catalog contribution. |

ELI5
If you are signed into Antigravity CLI (
agy) on the computer that runs Orca, Accounts now shows that Google session and the same 5-hour / weekly usage windows asagy /usage. It no longer pretends Antigravity is “Gemini CLI OAuth, experimental toggle off.”What Changed
~/.gemini/antigravity-cli(token file + optional email), the same “existing CLI login” pattern as Grok.agybinary (extracted at runtime; secrets are not committed). ConcatenatedGOCSPX-strings in the binary are split, and every id/secret pair is tried.daily-cloudcode-pa.googleapis.comretrieveUserQuotaSummarywithUser-Agent: antigravity. Without that UA, Google returns 403 “valid license of this product”; the productioncloudcode-pahost reports fake 0% used.antigravityAuthConfigured), and the web stub (unsigned).Why
Accounts never listed Antigravity, so a signed-in
agysession looked missing. Status-bar usage was a Gemini OAuth mirror gated ongeminiCliOAuthEnabled(off by default). Refresh then failed silently: an expired token plus a glued client secret returnederror, and the UI only rendered a usage row forunavailable.Linked Issue
Fixes #19587
Visual Proof
Before: Accounts showed a signed-in Antigravity card (email) and Refresh usage left no percentage row.
After: Refresh loads named Gemini / Claude+GPT 5-hour and weekly buckets from the daily quota summary (same windows as
agy /usage). A failed fetch now shows the error on the card instead of looking like a no-op.I do not have a committed after screenshot (UI assets must not land in the tree). Repro: Settings → AI Provider Accounts → Antigravity → Refresh usage on a machine with
agysigned in.Testing
agy1.1.27, consumer Google session)Targeted vitest: OAuth extractor (concatenated secrets + cartesian pairs), quota-summary parser, usage fetcher (daily host + Antigravity UA + 401 retry), Accounts section (error + buckets), rate-limit service wiring.
SSH / web: usage is read from the Orca runtime host home directory. The web preload stub stays unsigned. WSL home is not scanned (same as Grok). Windows uses
where.exeand%LOCALAPPDATA%\agy\bin\agy.exe.AI Disclosure
Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
OAuth client id/secret stay extracted from
agyat runtime and are never committed. Quota calls sendUser-Agent: antigravitybecause Cloud Code rejects Electron’s Chrome UA with a license 403.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)Made with Cursor