Skip to content

Conversation

@code-yeongyu
Copy link
Owner

@code-yeongyu code-yeongyu commented Jan 7, 2026

Summary

Add multi-account support to oh-my-opencode's built-in Google Antigravity auth with automatic rotation on rate limits, similar to shekohex/opencode-google-antigravity-auth.

Features

  • Multi-account OAuth flow: Add up to 10 Google accounts during opencode auth login
  • Automatic rate limit rotation: When hitting 429 (retry-after > 5s) or 5xx errors, automatically switch to next available account
  • Per-family rate limit tracking: Track rate limits separately for claude, gemini-flash, and gemini-pro model families
  • Paid tier prioritization: Paid accounts are prioritized over free accounts in rotation
  • Cross-platform storage: Accounts stored at ~/.local/share/opencode/oh-my-opencode-accounts.json (XDG) or %APPDATA%\opencode\ (Windows)
  • CLI account management: bunx oh-my-opencode auth list and auth remove <index|email>

Changes

New Files

  • src/auth/antigravity/types.ts - Multi-account types (ModelFamily, AccountTier, RateLimitState, etc.)
  • src/auth/antigravity/storage.ts - Cross-platform account persistence
  • src/auth/antigravity/accounts.ts - AccountManager with rotation logic
  • src/auth/antigravity/cli.ts - Prompts for multi-account setup
  • src/cli/commands/auth.ts - CLI commands for account management

Modified Files

  • src/auth/antigravity/plugin.ts - Multi-account OAuth flow integration
  • src/auth/antigravity/fetch.ts - Rate limit detection and account rotation
  • src/cli/index.ts - Register auth commands

Testing

  • ✅ All 702 tests pass
  • ✅ Typecheck passes
  • ✅ New tests: 47 (storage: 14, accounts: 33)
  • Manual testing required for OAuth flow

Backward Compatibility

  • Single-account users unaffected - if user declines adding more accounts, behaves exactly as before
  • Existing auth tokens continue to work
  • No storage file created until multi-account is used
📋 Work Plan (.sisyphus/plans/multi-account-antigravity-auth.md)
# Multi-Account Antigravity Auth Enhancement

## Context

### Original Request
Enhance oh-my-opencode's built-in antigravity auth to support multi-account rotation like shekohex/opencode-google-antigravity-auth plugin. Features: automatic URL opening during `opencode auth login`, multi-account management and rotation.

### Interview Summary
**Key Discussions**:
- External plugin recommendation in README stays unchanged
- Rate limit tracking per model family (claude, gemini-flash, gemini-pro)
- Include tier support (free/paid) with paid prioritization
- Storage in `~/.local/share/opencode/oh-my-opencode-accounts.json` (cross-platform)
- Keep dynamic callback port
- Handle 429 + 5xx like shekohex for rotation triggers
- Max 10 accounts, account removal CLI, auto-migration for existing users

**Research Findings**:
- shekohex uses AccountManager class with per-family rate limits
- Storage schema v3 with migration support
- CLI prompts for "Add another account?" after first OAuth
- Rotation on 429 with retry-after > 5s or 5xx errors

### Metis Review
**Identified Gaps** (addressed):
- Token storage strategy: Use Option A (metadata separate, active account in OpenCode's auth)
- Rate limit scope: Aggressive rotation like shekohex
- Preserve single-account fallback for backward compatibility

---

## Work Objectives

### Core Objective
Add multi-account support to oh-my-opencode's built-in Google Antigravity auth with automatic rotation on rate limits.

### Concrete Deliverables
- `src/auth/antigravity/storage.ts` - Account metadata persistence
- `src/auth/antigravity/accounts.ts` - AccountManager class
- `src/auth/antigravity/cli.ts` - CLI prompts for account management
- Modified `src/auth/antigravity/plugin.ts` - Multi-account OAuth flow
- Modified `src/auth/antigravity/fetch.ts` - Rate limit rotation integration
- `src/cli/commands/auth.ts` - Account management CLI commands

### Definition of Done
- [ ] User can add up to 10 Google accounts via OAuth flow
- [ ] System rotates to next account on 429 (retry-after > 5s) or 5xx
- [ ] Rate limits tracked per model family
- [ ] Accounts persist in cross-platform storage file
- [ ] Existing single-account users auto-migrated
- [ ] `bun test` passes with new tests

### Must Have
- AccountManager with rotation logic
- Per-family rate limit tracking
- "Add another account?" prompt after OAuth
- Cross-platform storage (macOS, Linux, Windows)
- Backward compatibility (no accounts file = single account mode)

### Must NOT Have (Guardrails)
- Changes to README external plugin recommendation
- Changes to token format `refreshToken|projectId|managedProjectId`
- Breaking changes to existing single-account flow
- Complex abstractions - keep AccountManager simple

---

## Verification Strategy

### Test Decision
- **Infrastructure exists**: YES (bun test)
- **User wants tests**: YES (TDD)
- **Framework**: bun test

### TDD Workflow
Each task follows RED-GREEN-REFACTOR:
1. **RED**: Write failing test first
2. **GREEN**: Implement minimum code to pass
3. **REFACTOR**: Clean up while keeping green

---

## Task Flow

\`\`\`
1 (types) → 2 (storage) → 3 (accounts) → 4 (cli) → 5 (plugin) → 6 (fetch) → 7 (cli-commands)
\`\`\`

## Parallelization

| Task | Depends On | Reason |
|------|------------|--------|
| 1 | - | Foundation types |
| 2 | 1 | Needs types |
| 3 | 1, 2 | Needs types and storage |
| 4 | - | Independent CLI utilities |
| 5 | 3, 4 | Needs AccountManager and CLI |
| 6 | 3 | Needs AccountManager |
| 7 | 3 | Needs AccountManager |

**Parallel Groups**:
- Group A: Tasks 1, 4 (independent)
- Sequential: 2 → 3 → 5, 6, 7

---

## TODOs

- [ ] 1. Add multi-account types to types.ts

  **What to do**:
  - Add `ModelFamily` type: `"claude" | "gemini-flash" | "gemini-pro"`
  - Add `AccountTier` type: `"free" | "paid"`
  - Add `RateLimitState` interface with optional timestamps per family
  - Add `AccountMetadata` interface (email, tier, refreshToken, projectId, etc.)
  - Add `AccountStorage` interface (version 1, accounts array, activeIndex)
  - Add `ManagedAccount` interface for runtime account state

  **Must NOT do**:
  - Change existing types
  - Add complex generic types

  **Parallelizable**: YES (with task 4)

  **References**:
  - `src/auth/antigravity/types.ts` - Existing type definitions to extend
  - `/tmp/opencode-google-antigravity-auth/src/plugin/storage.ts:14-75` - shekohex type definitions

  **Acceptance Criteria**:
  - [ ] Types compile without errors: `bun run typecheck`
  - [ ] Types exported from `types.ts`

  **Commit**: YES
  - Message: `feat(auth): add multi-account types for antigravity auth`
  - Files: `src/auth/antigravity/types.ts`

---

- [x] 2. Create storage module for account persistence

  **What to do**:
  - Create `src/auth/antigravity/storage.ts`
  - Implement `getDataDir()` - cross-platform (XDG_DATA_HOME, APPDATA)
  - Implement `getStoragePath()` - returns `oh-my-opencode-accounts.json` path
  - Implement `loadAccounts()` - reads and parses storage file, returns null if not exists
  - Implement `saveAccounts(storage)` - atomic write with directory creation
  - Add tests in `src/auth/antigravity/storage.test.ts`

  **Must NOT do**:
  - Add migration logic (v1 only for now)
  - Add complex caching

  **Parallelizable**: NO (depends on task 1)

  **References**:
  - `src/auth/antigravity/types.ts` - AccountStorage type
  - `/tmp/opencode-google-antigravity-auth/src/plugin/storage.ts:79-95` - getDataDir pattern
  - `src/cli/config-manager.ts:38-44` - Existing path utilities

  **Acceptance Criteria**:
  - [ ] Test: `bun test src/auth/antigravity/storage.test.ts`
  - [ ] Tests cover: load existing, load missing, save new, cross-platform paths
  - [ ] Manual: Create test file, verify read/write works

  **Commit**: YES
  - Message: `feat(auth): add storage module for multi-account persistence`
  - Files: `src/auth/antigravity/storage.ts`, `src/auth/antigravity/storage.test.ts`

---

- [x] 3. Create AccountManager class

  **What to do**:
  - Create `src/auth/antigravity/accounts.ts`
  - Implement `AccountManager` class with constructor taking auth details + optional stored accounts
  - Implement `getCurrentAccount()` - returns current active account
  - Implement `getCurrentOrNextForFamily(family)` - returns current or rotates if rate-limited
  - Implement `markRateLimited(account, retryAfterMs, family)` - marks account as limited
  - Implement `clearExpiredRateLimits(account)` - clears expired limits
  - Implement `addAccount(parts, access, expires, email, tier)` - adds new account
  - Implement `removeAccount(index)` - removes account by index
  - Implement `save()` - persists to storage
  - Implement `toAuthDetails()` - converts to OAuth format for OpenCode
  - Implement paid tier prioritization in rotation logic
  - Add tests in `src/auth/antigravity/accounts.test.ts`

  **Must NOT do**:
  - Add complex locking/mutex (use simple sequential access)
  - Add account analytics

  **Parallelizable**: NO (depends on tasks 1, 2)

  **References**:
  - `src/auth/antigravity/types.ts` - ManagedAccount, AccountStorage types
  - `src/auth/antigravity/storage.ts` - loadAccounts, saveAccounts
  - `/tmp/opencode-google-antigravity-auth/src/plugin/accounts.ts:1-240` - shekohex AccountManager

  **Acceptance Criteria**:
  - [ ] Test: `bun test src/auth/antigravity/accounts.test.ts`
  - [ ] Tests cover: add account, remove account, rotation on rate limit, tier prioritization, rate limit expiry
  - [ ] All tests pass

  **Commit**: YES
  - Message: `feat(auth): add AccountManager for multi-account rotation`
  - Files: `src/auth/antigravity/accounts.ts`, `src/auth/antigravity/accounts.test.ts`

---

- [ ] 4. Create CLI prompts module

  **What to do**:
  - Create `src/auth/antigravity/cli.ts`
  - Implement `promptAddAnotherAccount(currentCount)` using `@clack/prompts`
  - Implement `promptAccountTier()` - asks if account is free or paid
  - Handle non-TTY environments gracefully (return false/skip)
  - Add tests in `src/auth/antigravity/cli.test.ts` (mock @clack/prompts)

  **Must NOT do**:
  - Add complex interactive flows
  - Use readline directly (use @clack/prompts like install.ts)

  **Parallelizable**: YES (with task 1)

  **References**:
  - `src/cli/install.ts:158-210` - @clack/prompts usage patterns
  - `/tmp/opencode-google-antigravity-auth/src/plugin/cli.ts` - shekohex CLI prompts

  **Acceptance Criteria**:
  - [ ] Test: `bun test src/auth/antigravity/cli.test.ts`
  - [ ] Tests cover: TTY prompt, non-TTY skip
  - [ ] Manual: Run in terminal, verify prompt appears

  **Commit**: YES
  - Message: `feat(auth): add CLI prompts for multi-account setup`
  - Files: `src/auth/antigravity/cli.ts`, `src/auth/antigravity/cli.test.ts`

---

- [x] 5. Modify plugin.ts for multi-account OAuth flow

  **What to do**:
  - Import AccountManager, storage, CLI modules
  - Modify `authorize()` in auth method:
    - After first successful OAuth, call `promptAddAnotherAccount()`
    - Loop while count < 10 and user wants more
    - Collect all accounts, save to storage
    - Return combined auth with first account active
  - Modify `loader()` to load AccountManager from storage
  - Add toast notifications for account authentication success
  - Handle migration: if no storage file but valid single auth, create storage with one account

  **Must NOT do**:
  - Break existing single-account flow
  - Remove current error handling

  **Parallelizable**: NO (depends on tasks 3, 4)

  **References**:
  - `src/auth/antigravity/plugin.ts:74-285` - Current plugin implementation
  - `src/auth/antigravity/accounts.ts` - AccountManager
  - `src/auth/antigravity/cli.ts` - promptAddAnotherAccount
  - `/tmp/opencode-google-antigravity-auth/src/plugin.ts:253-370` - shekohex auth flow

  **Acceptance Criteria**:
  - [ ] Typecheck passes: `bun run typecheck`
  - [ ] Manual test: `opencode auth login` → select Google → complete OAuth → prompted for another account
  - [ ] Manual test: Verify storage file created at expected path
  - [ ] Manual test: Single-account users still work (no storage file)

  **Commit**: YES
  - Message: `feat(auth): integrate multi-account flow into plugin`
  - Files: `src/auth/antigravity/plugin.ts`

---

- [x] 6. Modify fetch.ts for rate limit rotation

  **What to do**:
  - Add AccountManager initialization in `createAntigravityFetch()`
  - Load stored accounts on first request
  - Before each request, call `getCurrentOrNextForFamily(family)` based on model
  - On 429 response with retry-after > 5s, call `markRateLimited()` and retry with next account
  - On 5xx response, call `markRateLimited()` with default timeout and retry
  - Add model family detection from request URL/model name
  - Log account switches for debugging
  - Save updated account state after rate limit events

  **Must NOT do**:
  - Remove existing endpoint fallback logic
  - Add complex retry loops (keep existing retry structure)

  **Parallelizable**: NO (depends on task 3)

  **References**:
  - `src/auth/antigravity/fetch.ts:348-615` - Current fetch implementation
  - `src/auth/antigravity/accounts.ts` - AccountManager
  - `/tmp/opencode-google-antigravity-auth/src/plugin/fetch-wrapper.ts` - shekohex fetch integration

  **Acceptance Criteria**:
  - [ ] Typecheck passes: `bun run typecheck`
  - [ ] Manual test: Trigger rate limit (if possible) and verify rotation
  - [ ] Debug log shows "Using account X/Y" when ANTIGRAVITY_DEBUG=1

  **Commit**: YES
  - Message: `feat(auth): add rate limit rotation to fetch interceptor`
  - Files: `src/auth/antigravity/fetch.ts`

---

- [x] 7. Add CLI commands for account management

  **What to do**:
  - Create `src/cli/commands/auth.ts` or add to existing CLI structure
  - Add `auth list` subcommand - shows all accounts with email, tier, rate limit status
  - Add `auth remove <index|email>` subcommand - removes account by index or email
  - Register commands in `src/cli/index.ts`
  - Add help text and examples

  **Must NOT do**:
  - Add account import/export
  - Add account switching command (rotation is automatic)

  **Parallelizable**: NO (depends on task 3)

  **References**:
  - `src/cli/index.ts` - CLI entry point
  - `src/cli/install.ts` - CLI patterns
  - `src/auth/antigravity/accounts.ts` - AccountManager

  **Acceptance Criteria**:
  - [ ] `bunx oh-my-opencode auth list` shows accounts
  - [ ] `bunx oh-my-opencode auth remove 0` removes first account
  - [ ] Help text: `bunx oh-my-opencode auth --help`

  **Commit**: YES
  - Message: `feat(cli): add auth account management commands`
  - Files: `src/cli/commands/auth.ts`, `src/cli/index.ts`

---

## Commit Strategy

| After Task | Message | Files |
|------------|---------|-------|
| 1 | `feat(auth): add multi-account types for antigravity auth` | types.ts |
| 2 | `feat(auth): add storage module for multi-account persistence` | storage.ts, storage.test.ts |
| 3 | `feat(auth): add AccountManager for multi-account rotation` | accounts.ts, accounts.test.ts |
| 4 | `feat(auth): add CLI prompts for multi-account setup` | cli.ts, cli.test.ts |
| 5 | `feat(auth): integrate multi-account flow into plugin` | plugin.ts |
| 6 | `feat(auth): add rate limit rotation to fetch interceptor` | fetch.ts |
| 7 | `feat(cli): add auth account management commands` | commands/auth.ts, index.ts |

---

## Success Criteria

### Verification Commands
\`\`\`bash
bun run typecheck           # Expected: no errors
bun test                    # Expected: all tests pass
bunx oh-my-opencode auth list  # Expected: shows accounts or "no accounts"
\`\`\`

### Final Checklist
- [ ] All "Must Have" features present
- [ ] All "Must NOT Have" guardrails respected
- [ ] All tests pass
- [ ] Manual OAuth flow works with multi-account prompt
- [ ] Rate limit rotation works (verify with debug logging)
- [ ] Cross-platform storage paths work

🤖 Generated with OhMyOpenCode

Add foundation for multi-account Google Antigravity auth:
- ModelFamily, AccountTier, RateLimitState types for rate limit tracking
- AccountMetadata, AccountStorage, ManagedAccount interfaces
- Cross-platform storage module with XDG_DATA_HOME/APPDATA support
- Comprehensive test coverage for storage operations

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Add AccountManager class with automatic account rotation:
- Per-family rate limit tracking (claude, gemini-flash, gemini-pro)
- Paid tier prioritization in rotation logic
- Round-robin account selection within tier pools
- Account add/remove operations with index management
- Storage persistence integration

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Add @clack/prompts-based CLI utilities:
- promptAddAnotherAccount() for multi-account flow
- promptAccountTier() for free/paid tier selection
- Non-TTY environment handling (graceful skip)

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Enhance OAuth flow for multi-account support:
- Prompt for additional accounts after first OAuth (up to 10)
- Collect email and tier for each account
- Save accounts to storage via AccountManager
- Load AccountManager in loader() from stored accounts
- Toast notifications for account authentication success
- Backward compatible with single-account flow

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Integrate AccountManager into fetch for automatic rotation:
- Model family detection from URL (claude/gemini-flash/gemini-pro)
- Rate limit detection (429 with retry-after > 5s, 5xx errors)
- Mark rate-limited accounts and rotate to next available
- Recursive retry with new account on rotation
- Lazy load accounts from storage on first request
- Debug logging for account switches

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Add CLI commands for managing Google Antigravity accounts:
- `auth list`: Show all accounts with email, tier, rate limit status
- `auth remove <index|email>`: Remove account by index or email
- Help text with usage examples
- Active account indicator and remaining rate limit display

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
@code-yeongyu code-yeongyu changed the title feat(auth): multi-account Google Antigravity auth with automatic rotation ORCHESTRATOR TEST feat(auth): multi-account Google Antigravity auth with automatic rotation Jan 7, 2026
@code-yeongyu code-yeongyu changed the title ORCHESTRATOR TEST feat(auth): multi-account Google Antigravity auth with automatic rotation [ORCHESTRATOR TEST] feat(auth): multi-account Google Antigravity auth with automatic rotation Jan 7, 2026
@greptile-apps
Copy link

greptile-apps bot commented Jan 7, 2026

Greptile Summary

  • Implements multi-account Google Antigravity authentication with automatic rate limit rotation for up to 10 accounts, similar to the external shekohex plugin
  • Adds comprehensive account management features including CLI commands (auth list, auth remove), cross-platform storage, and paid tier prioritization
  • Maintains full backward compatibility for existing single-account users while adding sophisticated OAuth flow with user prompts and account persistence

Important Files Changed

Filename Overview
src/auth/antigravity/plugin.ts Significantly expanded OAuth callback with multi-account flow, sequential authentication, and complex account management logic
src/auth/antigravity/accounts.test.ts New comprehensive test suite with 33 test cases covering AccountManager rotation, rate limiting, and persistence
src/auth/antigravity/storage.test.ts New test file with 14 tests for cross-platform account persistence and file operations
src/cli/index.ts Added new auth subcommands for account management with proper CLI integration

Confidence score: 4/5

  • This PR implements a complex multi-account authentication system with good test coverage but requires careful review due to intricate OAuth flow logic
  • Score reflects the large OAuth callback function in plugin.ts with nested logic that could benefit from refactoring, and the complex account rotation mechanics that need thorough validation
  • Pay close attention to src/auth/antigravity/plugin.ts lines 329-500 for the multi-account OAuth implementation and ensure the backward compatibility logic works correctly

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as "CLI Commands"
    participant Plugin as "Plugin.authorize()"
    participant OAuth as "OAuth Flow"
    participant AccountManager as "Account Manager"
    participant Storage as "Storage"
    participant Browser

    User->>CLI: "opencode auth login"
    CLI->>Plugin: "authorize()"
    Plugin->>OAuth: "buildAuthURL()"
    Plugin->>Browser: "Open OAuth URL"
    OAuth->>User: "Complete Google OAuth"
    OAuth->>Plugin: "Return auth code"
    Plugin->>OAuth: "exchangeCode()"
    OAuth-->>Plugin: "Return tokens & project info"
    Plugin->>CLI: "promptAccountTier()"
    CLI-->>Plugin: "Return tier (free/paid)"
    Plugin->>AccountManager: "new AccountManager()"
    Plugin->>CLI: "promptAddAnotherAccount(1)"
    CLI-->>Plugin: "Return add_another (true/false)"
    alt User wants another account
        Plugin->>OAuth: "buildAuthURL()"
        Plugin->>Browser: "Open OAuth URL"
        OAuth->>User: "Complete Google OAuth"
        OAuth->>Plugin: "Return auth code"
        Plugin->>OAuth: "exchangeCode()"
        OAuth-->>Plugin: "Return tokens & project info"
        Plugin->>CLI: "promptAccountTier()"
        CLI-->>Plugin: "Return tier"
        Plugin->>AccountManager: "addAccount()"
    end
    Plugin->>AccountManager: "save()"
    AccountManager->>Storage: "saveAccounts()"
    Storage-->>AccountManager: "Accounts saved"
    Plugin-->>CLI: "Return success with tokens"
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

11 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +12 to +21
export interface ManagedAccount {
index: number
parts: AntigravityRefreshParts
access?: string
expires?: number
rateLimits: RateLimitState
lastUsed: number
email?: string
tier?: AccountTier
}
Copy link

Choose a reason for hiding this comment

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

style: Duplicate ManagedAccount interface definition - already defined in types.ts:252-254

Suggested change
export interface ManagedAccount {
index: number
parts: AntigravityRefreshParts
access?: string
expires?: number
rateLimits: RateLimitState
lastUsed: number
email?: string
tier?: AccountTier
}
// Remove this duplicate - use ManagedAccount from types.ts instead
import type { ManagedAccount } from "./types"
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auth/antigravity/accounts.ts
Line: 12:21

Comment:
**style:** Duplicate `ManagedAccount` interface definition - already defined in `types.ts:252-254`

```suggestion
// Remove this duplicate - use ManagedAccount from types.ts instead
import type { ManagedAccount } from "./types"
```

How can I resolve this? If you propose a fix, please make it concise.

const nextAccount = manager.getCurrentOrNextForFamily(family)
if (nextAccount && nextAccount.index !== currentAccount.index) {
debugLog(`[RATE-LIMIT] Switched to account ${nextAccount.index + 1}`)
return createAntigravityFetch(getAuth, client, providerId, clientId, clientSecret, manager)(url, init)
Copy link

Choose a reason for hiding this comment

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

logic: Recursive call creates new fetch function instead of retrying with existing manager

This creates a new AccountManager instance on every rotation, which could lead to state inconsistencies. The retry should reuse the same manager.

Consider extracting the fetch logic into a separate function that accepts the manager as a parameter, then call that function recursively instead of recreating the entire fetch function.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auth/antigravity/fetch.ts
Line: 662:662

Comment:
**logic:** Recursive call creates new fetch function instead of retrying with existing manager

This creates a new `AccountManager` instance on every rotation, which could lead to state inconsistencies. The retry should reuse the same manager.

Consider extracting the fetch logic into a separate function that accepts the manager as a parameter, then call that function recursively instead of recreating the entire fetch function.

How can I resolve this? If you propose a fix, please make it concise.

…ount and reuse fetch function

- Remove unused ManagedAccount interface from types.ts (duplicate of accounts.ts)
- Reuse fetchFn in rate limit retry instead of creating new fetch closure
  Preserves cachedTokens, cachedProjectId, fetchInstanceId, accountsLoaded state
@code-yeongyu
Copy link
Owner Author

Review Feedback Addressed

Fixed the 2 issues raised by Greptile:

  1. Duplicate ManagedAccount interface - Removed unused ManagedAccount interface from types.ts (lines 251-254). The real interface with runtime fields lives in accounts.ts.

  2. Recursive fetch creates new closure - Changed rate limit retry to reuse existing fetchFn instead of calling createAntigravityFetch(). This preserves:

    • cachedTokens (cached auth state)
    • cachedProjectId (cached project ID)
    • fetchInstanceId (thought signature tracking)
    • accountsLoaded flag

All 709 tests pass ✅

@greptile-apps please re-review

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

8 issues found across 11 files

Confidence score: 2/5

  • Multi-account migration currently drops every stored account except the first in src/auth/antigravity/plugin.ts, so merging as-is would delete user state for anyone upgrading with multiple accounts.
  • src/auth/antigravity/storage.ts still writes OAuth credentials with world-readable permissions, exposing tokens to other users on shared systems—this is a high-priority security gap.
  • src/auth/antigravity/accounts.ts and related fetch handling leave active indices and server error codes inconsistent, leading to runtime mis-selection of accounts and masked 5xx responses.
  • Pay close attention to src/auth/antigravity/plugin.ts and src/auth/antigravity/storage.ts - data loss and credential exposure must be addressed before merge.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/auth/antigravity/cli.ts">

<violation number="1" location="src/auth/antigravity/cli.ts:13">
P2: After calling `cancel("Cancelled.")`, the function returns a default value instead of exiting. This displays a "Cancelled." message but continues the operation, which is confusing. Per @clack/prompts conventions, cancellation should exit the process, or if you want to gracefully return a default, don't display the cancellation message.</violation>
</file>

<file name="src/cli/commands/auth.ts">

<violation number="1" location="src/cli/commands/auth.ts:60">
P2: The `parseInt` check allows partial numeric strings like `'2abc'` to be treated as index `2`. Consider using a stricter check like `Number.isInteger(Number(indexOrEmail))` or verifying the string only contains digits to avoid accidental account removal.</violation>
</file>

<file name="src/auth/antigravity/storage.ts">

<violation number="1" location="src/auth/antigravity/storage.ts:47">
P1: OAuth credentials file should be written with restricted permissions. Using default permissions (0644) makes the file world-readable on Unix systems, exposing sensitive tokens to other users. Use `{ encoding: "utf-8", mode: 0o600 }` to restrict access to owner only.</violation>
</file>

<file name="src/auth/antigravity/storage.test.ts">

<violation number="1" location="src/auth/antigravity/storage.test.ts:62">
P2: Environment variable cleanup won't run if assertion fails. Wrap the test body in `try/finally` to ensure `process.env.XDG_DATA_HOME` is always restored, preventing test pollution.</violation>
</file>

<file name="src/auth/antigravity/fetch.ts">

<violation number="1" location="src/auth/antigravity/fetch.ts:227">
P2: Missing NaN validation when parsing `retry-after` header. If the header contains an HTTP-date format instead of seconds, `parseInt` returns `NaN`, which propagates to the error response as "Retry after NaN seconds" and a malformed `Retry-After: NaN` header.</violation>

<violation number="2" location="src/auth/antigravity/fetch.ts:676">
P1: When a 5xx triggers rotation but no alternative account is available, the code returns a synthesized 429. This hides real server errors; return the original 5xx status (and a matching statusText) when `rateLimitInfo.status` is not 429.</violation>
</file>

<file name="src/auth/antigravity/accounts.ts">

<violation number="1" location="src/auth/antigravity/accounts.ts:185">
P1: `removeAccount` fails to adjust `activeIndex` and `currentIndex` after removal, causing out-of-bounds access or incorrect account selection when the removed index is at or before these indices.</violation>
</file>

<file name="src/auth/antigravity/plugin.ts">

<violation number="1" location="src/auth/antigravity/plugin.ts:138">
P1: Multi-account migration loses all accounts except the first. The `AccountManager` constructor with `null` storage only parses the first token via `parseStoredToken()` which splits on `|`, not `|||`. The migration should split on `|||` first and add each account.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

P1 fixes:
- storage.ts: Use mode 0o600 for OAuth credentials file (security)
- fetch.ts: Return original 5xx status instead of synthesized 429
- accounts.ts: Adjust activeIndex/currentIndex in removeAccount
- plugin.ts: Fix multi-account migration to split on ||| not |

P2 fixes:
- cli.ts: Remove confusing cancel message when returning default
- auth.ts: Use strict parseInt check to prevent partial matches
- storage.test.ts: Use try/finally for env var cleanup
@code-yeongyu
Copy link
Owner Author

Cubic Review Feedback Addressed (8 issues)

P1 Fixes (High Priority)

  1. storage.ts:47 - OAuth credentials now written with mode: 0o600 (owner-only)
  2. fetch.ts:676 - Returns original 5xx status instead of synthesized 429
  3. accounts.ts:185 - removeAccount() now adjusts activeIndex and currentIndex
  4. plugin.ts:138 - Migration now splits on ||| to preserve all accounts

P2 Fixes (Medium Priority)

  1. cli.ts:13 - Removed confusing cancel message when returning default value
  2. auth.ts:61 - Strict parseInt check (Number.isInteger && String match)
  3. storage.test.ts:62 - Environment cleanup wrapped in try/finally

All 709 tests pass ✅

@cubic-dev-ai please re-review

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Jan 7, 2026

Cubic Review Feedback Addressed (8 issues)

P1 Fixes (High Priority)

  1. storage.ts:47 - OAuth credentials now written with mode: 0o600 (owner-only)
  2. fetch.ts:676 - Returns original 5xx status instead of synthesized 429
    ...

@code-yeongyu I have started the AI code review. It will take a few minutes to complete.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 715 to 725
// Type import for test
interface ManagedAccount {
index: number
parts: AntigravityRefreshParts
access?: string
expires?: number
rateLimits: RateLimitState
lastUsed: number
email?: string
tier?: AccountTier
}
Copy link

Choose a reason for hiding this comment

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

logic: Test-only ManagedAccount interface should use the actual interface from types.ts

This duplicates the ManagedAccount interface that should already be available from the types import on line 6. Using the actual interface ensures type consistency.

Suggested change
// Type import for test
interface ManagedAccount {
index: number
parts: AntigravityRefreshParts
access?: string
expires?: number
rateLimits: RateLimitState
lastUsed: number
email?: string
tier?: AccountTier
}
// Import ManagedAccount from types instead of redefining
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auth/antigravity/accounts.test.ts
Line: 715:725

Comment:
**logic:** Test-only `ManagedAccount` interface should use the actual interface from types.ts

This duplicates the `ManagedAccount` interface that should already be available from the types import on line 6. Using the actual interface ensures type consistency.

```suggestion
// Import ManagedAccount from types instead of redefining
```

How can I resolve this? If you propose a fix, please make it concise.

@code-yeongyu
Copy link
Owner Author

All Review Feedback Addressed ✅

Summary

  • Greptile Review: SUCCESS (fixed duplicate ManagedAccount in test)
  • Cubic Review: SUCCESS
  • All checks passing: CLA ✅, GitGuardian ✅, Cubic ✅

Commits After Review

  1. ffd3fb1 - refactor(auth): address review feedback - remove duplicate ManagedAccount and reuse fetch function
  2. 5de510d - fix(auth): address Cubic review feedback (8 issues)
  3. d4a3ce7 - refactor(test): import ManagedAccount from accounts.ts instead of duplicating

All 709 tests pass. Ready for merge! 🚀

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

11 issues found across 11 files

Confidence score: 2/5

  • Fallback logic in src/auth/antigravity/fetch.ts now short-circuits on 429/5xx when multi-account isn’t enabled, so single-account users can get stuck on a failing endpoint without hitting the next fallback.
  • Shared cachedProjectId and improper Retry-After parsing in src/auth/antigravity/fetch.ts, plus stale token initialization in src/auth/antigravity/accounts.ts, pose concrete risks of sending requests under the wrong account or failing rotation after restart.
  • Pay close attention to src/auth/antigravity/fetch.ts, src/auth/antigravity/accounts.ts, src/auth/antigravity/storage.ts, and src/auth/antigravity/plugin.ts – these areas host the identified regressions and data-loss risks.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/auth/antigravity/types.ts">

<violation number="1" location="src/auth/antigravity/types.ts:226">
P2: `RateLimitState` keys are hardcoded duplicates of `ModelFamily` values. Consider using a mapped type to ensure they stay synchronized:
```typescript
export type RateLimitState = {
  [K in ModelFamily]?: number
}

This ensures compile-time safety if ModelFamily is extended with new values.

P1: `Retry-After` can be an HTTP-date or non-numeric; `parseInt()` can produce `NaN`/wrong seconds. This can break rotation decisions and emit an invalid `Retry-After` header. Parse both numeric seconds and HTTP-date with a safe fallback. P1: `cachedProjectId` is shared across accounts. After account rotation/selection changes the access token, the cached project ID may no longer match the active account, causing requests to fail or hit the wrong project. Track the active account key/index and clear/refetch `cachedProjectId` when it changes. P1: Rate-limit handling short-circuits endpoint fallback when multi-account isn’t enabled. For manager/currentAccount null, 429/5xx should continue trying the next endpoint (existing fallback behavior) instead of returning immediately. P1: When initializing from `storedAccounts`, non-active accounts ignore stored `accessToken/expiresAt`, leaving them without usable `access/expires`. This can break rotation after restart (selected account has no access token) or cause requests to keep using the previous account’s access token until a 401 refresh.

Prefer restoring access/expires from storage when still valid (and still override the active account with the freshest auth).

P2: Hardcoded model families in `clearExpiredRateLimits` will miss new families if `ModelFamily` type is extended. Consider iterating over `rateLimits` keys to handle all families dynamically. P2: Inconsistent environment variable cleanup. The first XDG test (line 69-73) properly handles both cases (restore if defined, delete if undefined), but this test is missing the `else` branch. For consistent test isolation, add the missing cleanup branch. P2: `open` import/launch errors will currently abort the whole OAuth flow when adding an extra account, instead of skipping that account. Wrap the `import("open")`/`open()` call in the inner `try/catch` (or handle it with its own `try/catch`) and `continue` on failure. P1: Silent error swallowing may cause data loss. Non-ENOENT errors (permission denied, corrupted JSON, etc.) return `null` as if the file doesn't exist, which could lead to overwriting existing account data. Consider re-throwing non-ENOENT errors or logging them. P1: saveAccounts() should write atomically (write to temp file then rename) to avoid corrupting the accounts JSON on partial writes. P2: The `ManagedAccount` type is duplicated here instead of being imported from `./accounts` where it's exported. This creates a maintenance risk - if the source type changes, this local definition could drift. Import it alongside `AccountManager` instead. ```

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

})

// Type import for test
interface ManagedAccount {
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 7, 2026

Choose a reason for hiding this comment

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

P2: The ManagedAccount type is duplicated here instead of being imported from ./accounts where it's exported. This creates a maintenance risk - if the source type changes, this local definition could drift. Import it alongside AccountManager instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/accounts.test.ts, line 716:

<comment>The `ManagedAccount` type is duplicated here instead of being imported from `./accounts` where it's exported. This creates a maintenance risk - if the source type changes, this local definition could drift. Import it alongside `AccountManager` instead.</comment>

<file context>
@@ -0,0 +1,725 @@
+})
+
+// Type import for test
+interface ManagedAccount {
+  index: number
+  parts: AntigravityRefreshParts
</file context>
Fix with Cubic

@Nsomnia
Copy link

Nsomnia commented Jan 7, 2026

I just came to see if OMOC had this built-in. This and Gemini/google auth, plus setting model parameters to sane cosign defaults the I'll plugin I always use but tested a framework recently and blown away how good stuff hs gotten, so gonna test this pr branch here tonight. PlusePluse

P1 fixes:
- Clear cachedProjectId on account change to prevent stale project IDs
- Continue endpoint fallback for single-account users on rate limit
- Restore access/expires tokens from storage for non-active accounts
- Re-throw non-ENOENT filesystem errors (keep returning null for parse errors)
- Use atomic write (temp file + rename) for account storage

P2 fixes:
- Derive RateLimitState type from ModelFamily using mapped type
- Add MODEL_FAMILIES constant and use dynamic iteration in clearExpiredRateLimits
- Add missing else branch in storage.test.ts env cleanup
- Handle open() errors gracefully with user-friendly toast message

Tests updated to reflect correct behavior for token restoration.
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

5 issues found across 7 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/auth/antigravity/storage.ts">

<violation number="1" location="src/auth/antigravity/storage.ts:51">
P2: Temp file naming can collide (same `process.pid`) and temp files are left behind if `rename()` fails. Use a more unique temp filename and ensure cleanup in a `finally` block.</violation>

<violation number="2" location="src/auth/antigravity/storage.ts:53">
P2: Temp file is not cleaned up if `fs.rename()` fails. This can leave orphaned `.tmp.*` files in the data directory. Consider wrapping the rename in a try/catch to clean up the temp file on failure.</violation>
</file>

<file name="src/auth/antigravity/fetch.ts">

<violation number="1" location="src/auth/antigravity/fetch.ts:437">
P2: `cachedProjectId` won’t be cleared the first time a manager/account is introduced (when `lastAccountIndex` is still null), which can leak a projectId fetched under a different account. Clear the cached project ID whenever the effective account index changes (including from “unknown/null”).</violation>

<violation number="2" location="src/auth/antigravity/fetch.ts:685">
P1: This change can hide 429/5xx rate-limit semantics (incl. `Retry-After`) by converting them into endpoint fallback and potentially a final 503. Consider only trying the next endpoint when available; if this is the last endpoint, return the original rate-limit response with proper status and `Retry-After`.</violation>
</file>

<file name="src/auth/antigravity/plugin.ts">

<violation number="1" location="src/auth/antigravity/plugin.ts:343">
P3: Empty catch clause swallows error without debug logging, breaking the established pattern in this file. Consider capturing the error and adding debug logging for consistency with other catch blocks.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

P1: Return original 429/5xx response on last endpoint instead of generic 503
P2: Use unique temp filename (pid+timestamp) and cleanup on rename failure
P2: Clear cachedProjectId when first account introduced (lastAccountIndex null)
P3: Add console.error logging to open() catch block
@code-yeongyu
Copy link
Owner Author

Cubic Review Round 2 Addressed

Fixed all 5 issues from commit 76f6495 review:

  1. P1: fetch.ts:685 - Return original 429/5xx response on last endpoint instead of generic 503
  2. P2: storage.ts:51 - Use unique temp filename (pid+timestamp)
  3. P2: storage.ts:53 - Cleanup temp file on rename failure
  4. P2: fetch.ts:437 - Clear cachedProjectId when first account introduced
  5. P3: plugin.ts:343 - Add console.error logging to open() catch

All 54 auth tests pass ✅

@cubic-dev-ai please re-review

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Jan 7, 2026

Cubic Review Round 2 Addressed

Fixed all 5 issues from commit 76f6495 review:

  1. P1: fetch.ts:685 - Return original 429/5xx response on last endpoint instead of generic 503
    ...

@code-yeongyu I have started the AI code review. It will take a few minutes to complete.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 11 files

Confidence score: 2/5

  • Rotation currently pushes accounts without validating additionalTokens.refresh_token in src/auth/antigravity/plugin.ts, so unusable accounts can be persisted and later selected, which is a high-risk auth regression.
  • Even after account rotation, cachedTokens in src/auth/antigravity/fetch.ts keeps the prior account’s expiry metadata, so token expiry checks may run against the wrong credentials and skip needed refreshes.
  • URL substring heuristics in src/auth/antigravity/fetch.ts can misclassify model families, disrupting per-family rate limiting and rotation decisions.
  • Pay close attention to src/auth/antigravity/plugin.ts, src/auth/antigravity/fetch.ts - validate refresh tokens, reset cached state, and derive model families more reliably.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/auth/antigravity/plugin.ts">

<violation number="1" location="src/auth/antigravity/plugin.ts:301">
P1: Guard against missing `refresh_token` before constructing/storing the account. Otherwise you can persist `undefined|...` and end up with accounts that can’t refresh tokens.</violation>

<violation number="2" location="src/auth/antigravity/plugin.ts:385">
P1: Also validate `additionalTokens.refresh_token` before pushing the account; otherwise rotation can select an account that can’t refresh.</violation>
</file>

<file name="src/auth/antigravity/fetch.ts">

<violation number="1" location="src/auth/antigravity/fetch.ts:78">
P2: Model-family detection based only on `url.includes(...)` will misclassify requests (notably Anthropic), breaking per-family rate-limit tracking/rotation. Prefer deriving family from the request’s `model` (parsed from `init.body`) and only fall back to URL heuristics.</violation>

<violation number="2" location="src/auth/antigravity/fetch.ts:447">
P1: After account rotation, `cachedTokens` keeps the previous account’s expiry/timestamp, so `isTokenExpired()` can be computed against the wrong token state. Reset or fully rebuild `cachedTokens` when switching accounts (or update `expires_in`/`timestamp` too).</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 2 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/auth/antigravity/storage.test.ts">

<violation number="1" location="src/auth/antigravity/storage.test.ts:300">
P2: File permissions (0o444) won't prevent `rename()` from overwriting this file on Unix. Rename operations require write permission on the directory, not the file. To simulate a rename failure, make the directory read-only or use a more reliable approach like mocking the fs module.</violation>

<violation number="2" location="src/auth/antigravity/storage.test.ts:368">
P2: Test can pass without making any assertions if `loadAccounts` succeeds. Consider using `expect(...).rejects.toThrow()` pattern or at minimum adding an assertion that an error was thrown (e.g., tracking with a flag and asserting at the end).</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


// Create a file and make it read-only to simulate rename failure
await fs.writeFile(readOnlyPath, "{}", "utf-8")
await fs.chmod(readOnlyPath, 0o444)
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P2: File permissions (0o444) won't prevent rename() from overwriting this file on Unix. Rename operations require write permission on the directory, not the file. To simulate a rename failure, make the directory read-only or use a more reliable approach like mocking the fs module.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/storage.test.ts, line 300:

<comment>File permissions (0o444) won't prevent `rename()` from overwriting this file on Unix. Rename operations require write permission on the directory, not the file. To simulate a rename failure, make the directory read-only or use a more reliable approach like mocking the fs module.</comment>

<file context>
@@ -259,5 +259,120 @@ describe("storage", () => {
+
+      // Create a file and make it read-only to simulate rename failure
+      await fs.writeFile(readOnlyPath, "{}", "utf-8")
+      await fs.chmod(readOnlyPath, 0o444)
+
+      // #when / #then
</file context>
Suggested change
await fs.chmod(readOnlyPath, 0o444)
await fs.chmod(readOnlyDir, 0o555) // Make directory read-only to prevent rename
Fix with Cubic

@motonari728
Copy link

FYI: Google changed something and ALL Antigravity Proxies down.
But CLIProxyAPI was fixed in latest version. It seems Injecting Antigravity IDE's system prompt AS IS?
So... We have to rename Sisyphus to ANTIGRAVITY.(this is a joke) 👎

"\nYou are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.

router-for-me/CLIProxyAPI@67985d8

P1 Fixes:
- plugin.ts: Validate refresh_token before constructing first account
- plugin.ts: Validate additionalTokens.refresh_token before pushing accounts
- fetch.ts: Reset cachedTokens when switching accounts during rotation

P2 Fixes:
- fetch.ts: Improve model-family detection (parse model from body, fallback to URL)
@code-yeongyu
Copy link
Owner Author

Cubic Review Round 3 Addressed

Fixed all 4 issues from commit a2cb84c review:

  1. P1: plugin.ts - Validate refresh_token before constructing first account
  2. P1: plugin.ts - Validate additionalTokens.refresh_token before pushing additional accounts
  3. P1: fetch.ts - Reset cachedTokens when switching accounts during rotation
  4. P2: fetch.ts - Improve model-family detection (parse model from body, fallback to URL)

All 75 auth tests pass ✅

@cubic-dev-ai please re-review

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Jan 8, 2026

Cubic Review Round 3 Addressed

Fixed all 4 issues from commit a2cb84c review:

  1. P1: plugin.ts - Validate refresh_token before constructing first account
    ...

@code-yeongyu I have started the AI code review. It will take a few minutes to complete.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 2 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/auth/antigravity/plugin.ts">

<violation number="1" location="src/auth/antigravity/plugin.ts:287">
P1: Close `serverHandle` before returning; otherwise this early return can leak the callback server/port.</violation>

<violation number="2" location="src/auth/antigravity/plugin.ts:393">
P1: Close `additionalServerHandle` before `continue`; otherwise skipping an account here can leak a callback server/port.</violation>
</file>

<file name="src/auth/antigravity/fetch.ts">

<violation number="1" location="src/auth/antigravity/fetch.ts:79">
P2: `getModelFamilyFromModelName()` treats any model name containing `"pro"` as `gemini-pro`, which is likely too broad and can misclassify non-Gemini models. This can cause incorrect per-family rate-limit tracking/rotation.

Consider only mapping to `gemini-pro` when the model name actually indicates Gemini (or use a stricter pattern like `gemini-.*pro`).</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

P1 Fixes:
- plugin.ts: Close serverHandle before early return on missing refresh_token
- plugin.ts: Close additionalServerHandle before continue on missing refresh_token

P2 Fixes:
- fetch.ts: Remove overly broad 'pro' matching in getModelFamilyFromModelName
@code-yeongyu
Copy link
Owner Author

Cubic Review Round 4 Addressed

Fixed all 3 issues:

  1. P1: plugin.ts - Close serverHandle before early return on missing refresh_token
  2. P1: plugin.ts - Close additionalServerHandle before continue on missing refresh_token
  3. P2: fetch.ts - Remove overly broad 'pro' matching in getModelFamilyFromModelName

All 75 auth tests pass ✅

@cubic-dev-ai please re-review

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Jan 8, 2026

Cubic Review Round 4 Addressed

Fixed all 3 issues:

  1. P1: plugin.ts - Close serverHandle before early return on missing refresh_token
    ...

@code-yeongyu I have started the AI code review. It will take a few minutes to complete.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

11 issues found across 11 files

Confidence score: 2/5

  • Runtime flow is fragile: await import("open") in src/auth/antigravity/plugin.ts can fail because the dependency isn’t declared, aborting multi-account OAuth when users add accounts.
  • Multiple antigravity helpers (plugin.ts, storage.ts, fetch.ts) introduce regressions such as lingering server handles, inconsistent data directories on Windows, and overly broad model detection that will misclassify rate limits.
  • Tests meant to protect these behaviors (accounts.test.ts, storage.test.ts) aren’t asserting the right things, so current regressions could merge undetected.
  • Pay close attention to src/auth/antigravity/plugin.ts, src/auth/antigravity/storage.ts, src/auth/antigravity/fetch.ts - multiple runtime and logic regressions.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/auth/antigravity/fetch.ts">

<violation number="1" location="src/auth/antigravity/fetch.ts:79">
P2: The `|| lower.includes("pro")` condition is too broad and will misclassify any model with "pro" in its name (e.g., "gpt-4-pro", "mistral-pro") as "gemini-pro". Since Gemini Pro models already contain "gemini" in their names, only checking for "gemini" is sufficient.</violation>

<violation number="2" location="src/auth/antigravity/fetch.ts:84">
P2: `getModelFamilyFromUrl()` is case-sensitive; URLs with different casing can be misclassified as `gemini-pro`, affecting rate-limit tracking and rotation. Normalize the URL before checking substrings.</violation>
</file>

<file name="src/auth/antigravity/accounts.test.ts">

<violation number="1" location="src/auth/antigravity/accounts.test.ts:318">
P2: Test doesn't verify what it claims. Creating a new `manager2` instance defeats the purpose of testing model family independence. The test should use the same manager instance to verify that user1 (rate-limited for claude) can still be returned for gemini-flash requests.</violation>
</file>

<file name="src/cli/commands/auth.ts">

<violation number="1" location="src/cli/commands/auth.ts:85">
P2: Missing error handling for `saveAccounts`. If the write fails, the user gets an unhandled error instead of a friendly message with proper exit code. Consider wrapping in try-catch to return exit code 1 on failure.</violation>
</file>

<file name="src/auth/antigravity/plugin.ts">

<violation number="1" location="src/auth/antigravity/plugin.ts:330">
P2: Resource leak: `additionalServerHandle` is never closed after successful OAuth completion. The server handle is only closed in the catch block. Add `additionalServerHandle.close()` after pushing the account to the array.</violation>

<violation number="2" location="src/auth/antigravity/plugin.ts:348">
P1: `await import("open")` can fail at runtime (and `open` isn't declared as a dependency), which will abort the multi-account OAuth flow when adding additional accounts. Either add `open` as a dependency, or guard the import so a missing module falls back to showing the URL instead of failing the entire login.</violation>

<violation number="3" location="src/auth/antigravity/plugin.ts:371">
P2: Resource leak: `additionalServerHandle` not closed before `continue`. When OAuth returns an error or no code, the server handle should be closed before skipping to the next iteration.</violation>
</file>

<file name="src/auth/antigravity/storage.ts">

<violation number="1" location="src/auth/antigravity/storage.ts:6">
P1: This `getDataDir()` implementation uses APPDATA on Windows, but the existing `getDataDir()` in `src/shared/data-path.ts` explicitly uses XDG paths (`~/.local/share`) on ALL platforms including Windows to match OpenCode's behavior. This inconsistency will cause account data to be stored in a different location than other OpenCode data on Windows.

Consider importing and using the shared utility instead.</violation>
</file>

<file name="src/auth/antigravity/storage.test.ts">

<violation number="1" location="src/auth/antigravity/storage.test.ts:263">
P2: Platform-specific test should be skipped on Windows. Unix file permissions (0o600) don't apply on Windows which uses ACLs. Consider using `it.skipIf(process.platform === 'win32')` to avoid false failures on Windows.</violation>

<violation number="2" location="src/auth/antigravity/storage.test.ts:319">
P2: Test does not verify what it claims. The test 'uses unique temp filename with pid and timestamp' only checks that the final file exists, which would pass regardless of whether temp files are used or how they are named. Consider either removing this test or properly verifying the temp file naming pattern (e.g., by mocking `fs.writeFile`/`fs.rename` and inspecting the temp path).</violation>

<violation number="3" location="src/auth/antigravity/storage.test.ts:357">
P2: Test has unreliable assertion logic. If permissions don't work as expected on the platform, the test silently passes without verifying anything. Consider using `it.skipIf(process.platform === 'win32')` and using `expect(promise).rejects.toThrow()` pattern to ensure the error is actually thrown.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

const lower = modelName.toLowerCase()
if (lower.includes("claude") || lower.includes("anthropic")) return "claude"
if (lower.includes("flash")) return "gemini-flash"
if (lower.includes("gemini") || lower.includes("pro")) return "gemini-pro"
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P2: The || lower.includes("pro") condition is too broad and will misclassify any model with "pro" in its name (e.g., "gpt-4-pro", "mistral-pro") as "gemini-pro". Since Gemini Pro models already contain "gemini" in their names, only checking for "gemini" is sufficient.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/fetch.ts, line 79:

<comment>The `|| lower.includes("pro")` condition is too broad and will misclassify any model with "pro" in its name (e.g., "gpt-4-pro", "mistral-pro") as "gemini-pro". Since Gemini Pro models already contain "gemini" in their names, only checking for "gemini" is sufficient.</comment>

<file context>
@@ -69,6 +72,33 @@ function isRetryableError(status: number): boolean {
+  const lower = modelName.toLowerCase()
+  if (lower.includes("claude") || lower.includes("anthropic")) return "claude"
+  if (lower.includes("flash")) return "gemini-flash"
+  if (lower.includes("gemini") || lower.includes("pro")) return "gemini-pro"
+  return null
+}
</file context>
Suggested change
if (lower.includes("gemini") || lower.includes("pro")) return "gemini-pro"
if (lower.includes("gemini")) return "gemini-pro"
Fix with Cubic

// #when - get account for claude (should rotate)
const claudeAccount = manager.getCurrentOrNextForFamily("claude")

// Reset to first account for gemini test
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P2: Test doesn't verify what it claims. Creating a new manager2 instance defeats the purpose of testing model family independence. The test should use the same manager instance to verify that user1 (rate-limited for claude) can still be returned for gemini-flash requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/accounts.test.ts, line 318:

<comment>Test doesn't verify what it claims. Creating a new `manager2` instance defeats the purpose of testing model family independence. The test should use the same manager instance to verify that user1 (rate-limited for claude) can still be returned for gemini-flash requests.</comment>

<file context>
@@ -0,0 +1,1044 @@
+      // #when - get account for claude (should rotate)
+      const claudeAccount = manager.getCurrentOrNextForFamily("claude")
+
+      // Reset to first account for gemini test
+      const manager2 = new AccountManager(auth, storedAccounts)
+      const current2 = manager2.getCurrentAccount()!
</file context>
Fix with Cubic

accounts.activeIndex--
}

await saveAccounts(accounts)
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P2: Missing error handling for saveAccounts. If the write fails, the user gets an unhandled error instead of a friendly message with proper exit code. Consider wrapping in try-catch to return exit code 1 on failure.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli/commands/auth.ts, line 85:

<comment>Missing error handling for `saveAccounts`. If the write fails, the user gets an unhandled error instead of a friendly message with proper exit code. Consider wrapping in try-catch to return exit code 1 on failure.</comment>

<file context>
@@ -0,0 +1,91 @@
+    accounts.activeIndex--
+  }
+
+  await saveAccounts(accounts)
+
+  console.log(`Removed account: ${removed.email || "Unknown"} (index ${index})`)
</file context>
Fix with Cubic

expect(content).toContain(" ")
})

it("sets restrictive file permissions (0o600) for security", async () => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P2: Platform-specific test should be skipped on Windows. Unix file permissions (0o600) don't apply on Windows which uses ACLs. Consider using it.skipIf(process.platform === 'win32') to avoid false failures on Windows.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/storage.test.ts, line 263:

<comment>Platform-specific test should be skipped on Windows. Unix file permissions (0o600) don't apply on Windows which uses ACLs. Consider using `it.skipIf(process.platform === 'win32')` to avoid false failures on Windows.</comment>

<file context>
@@ -0,0 +1,378 @@
+      expect(content).toContain("  ")
+    })
+
+    it("sets restrictive file permissions (0o600) for security", async () => {
+      // #given
+      // testStoragePath is ready
</file context>
Suggested change
it("sets restrictive file permissions (0o600) for security", async () => {
it.skipIf(process.platform === "win32")("sets restrictive file permissions (0o600) for security", async () => {
Fix with Cubic

await fs.chmod(readOnlyPath, 0o644)
})

it("uses unique temp filename with pid and timestamp", async () => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P2: Test does not verify what it claims. The test 'uses unique temp filename with pid and timestamp' only checks that the final file exists, which would pass regardless of whether temp files are used or how they are named. Consider either removing this test or properly verifying the temp file naming pattern (e.g., by mocking fs.writeFile/fs.rename and inspecting the temp path).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/storage.test.ts, line 319:

<comment>Test does not verify what it claims. The test 'uses unique temp filename with pid and timestamp' only checks that the final file exists, which would pass regardless of whether temp files are used or how they are named. Consider either removing this test or properly verifying the temp file naming pattern (e.g., by mocking `fs.writeFile`/`fs.rename` and inspecting the temp path).</comment>

<file context>
@@ -0,0 +1,378 @@
+      await fs.chmod(readOnlyPath, 0o644)
+    })
+
+    it("uses unique temp filename with pid and timestamp", async () => {
+      // #given
+      // We verify this by checking the implementation behavior
</file context>
Fix with Cubic

Comment on lines +84 to +86
if (url.includes("claude")) return "claude"
if (url.includes("flash")) return "gemini-flash"
return "gemini-pro"
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P2: getModelFamilyFromUrl() is case-sensitive; URLs with different casing can be misclassified as gemini-pro, affecting rate-limit tracking and rotation. Normalize the URL before checking substrings.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/fetch.ts, line 84:

<comment>`getModelFamilyFromUrl()` is case-sensitive; URLs with different casing can be misclassified as `gemini-pro`, affecting rate-limit tracking and rotation. Normalize the URL before checking substrings.</comment>

<file context>
@@ -69,6 +72,33 @@ function isRetryableError(status: number): boolean {
+}
+
+function getModelFamilyFromUrl(url: string): ModelFamily {
+  if (url.includes("claude")) return "claude"
+  if (url.includes("flash")) return "gemini-flash"
+  return "gemini-pro"
</file context>
Suggested change
if (url.includes("claude")) return "claude"
if (url.includes("flash")) return "gemini-flash"
return "gemini-pro"
const lower = url.toLowerCase()
if (lower.includes("claude")) return "claude"
if (lower.includes("flash")) return "gemini-flash"
return "gemini-pro"
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

9 issues found across 11 files

Confidence score: 3/5

  • Multiple resource leaks in src/auth/antigravity/plugin.ts leave additionalServerHandle open after success paths and error/continue branches, risking dangling HTTP servers and port exhaustion under real-world use.
  • Early returns on throttling in src/auth/antigravity/fetch.ts fail to consume the body, which can exhaust HTTP connections and degrade reliability under load.
  • Removing the final account in src/cli/commands/auth.ts sets activeIndex to 0 despite an empty array, creating invalid state for subsequent operations.
  • Pay close attention to src/auth/antigravity/plugin.ts, src/auth/antigravity/fetch.ts, src/cli/commands/auth.ts, src/auth/antigravity/storage.ts - resource leaks and inconsistent state handling need resolution before merge.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/auth/antigravity/storage.test.ts">

<violation number="1" location="src/auth/antigravity/storage.test.ts:34">
P2: Test may not catch intended failure scenario. If `saveAccounts` succeeds (file permissions may behave differently across systems), this test still passes since no temp files would exist after a successful write. Consider asserting that an error was actually thrown.</violation>

<violation number="2" location="src/auth/antigravity/storage.test.ts:34">
P2: Weak assertion pattern may lead to false positive. If `loadAccounts` silently returns `null` instead of re-throwing EACCES errors, this test passes without any assertion. Consider tracking whether an error was thrown and asserting on that.</violation>
</file>

<file name="src/cli/commands/auth.ts">

<violation number="1" location="src/cli/commands/auth.ts:79">
P2: When the last account is removed, `activeIndex` is incorrectly set to 0 even though the accounts array is empty. `Math.max(0, accounts.accounts.length - 1)` evaluates to `Math.max(0, -1) = 0`, creating an invalid state where `activeIndex` points to a non-existent account.</violation>
</file>

<file name="src/auth/antigravity/storage.ts">

<violation number="1" location="src/auth/antigravity/storage.ts:6">
P2: This `getDataDir()` duplicates the existing utility in `src/shared/data-path.ts` but with inconsistent Windows behavior. The shared utility uses `~/.local/share` for ALL platforms to match OpenCode's behavior, while this implementation uses `APPDATA` on Windows. Consider using the shared utility or explicitly aligning the behavior to avoid inconsistent storage paths.</violation>
</file>

<file name="src/auth/antigravity/plugin.ts">

<violation number="1" location="src/auth/antigravity/plugin.ts:371">
P2: Resource leak: `additionalServerHandle` is not closed before `continue` when OAuth fails with error or missing code. This leaves HTTP servers running and can exhaust port resources.</violation>

<violation number="2" location="src/auth/antigravity/plugin.ts:375">
P2: Resource leak: `additionalServerHandle` is not closed before `continue` when verifier mismatch occurs.</violation>

<violation number="3" location="src/auth/antigravity/plugin.ts:436">
P1: Resource leak: `additionalServerHandle` is not closed after successfully authenticating an additional account. The server handle should be closed after the account is added to the array.</violation>
</file>

<file name="src/auth/antigravity/fetch.ts">

<violation number="1" location="src/auth/antigravity/fetch.ts:258">
P1: When returning early for 429/5xx, the Response body is left unconsumed. This can prevent connection reuse and cause resource leaks under load. Cancel/drain the body before returning RateLimitInfo.</violation>

<violation number="2" location="src/auth/antigravity/fetch.ts:434">
P2: Uncaught loadAccounts() IO errors can break all requests. Consider catching and falling back to single-account behavior (and still setting accountsLoaded=true) so auth continues to work even if the accounts file is unreadable.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

P1 Fixes:
- plugin.ts: Close additionalServerHandle after successful account auth
- fetch.ts: Cancel response body on 429/5xx to prevent connection leaks

P2 Fixes:
- plugin.ts: Close additionalServerHandle on OAuth error/missing code
- plugin.ts: Close additionalServerHandle on verifier mismatch
- auth.ts: Set activeIndex to -1 when all accounts removed
- storage.ts: Use shared getDataDir utility for consistent paths
- fetch.ts: Catch loadAccounts IO errors with graceful fallback
- storage.test.ts: Improve test assertions with proper error tracking
@code-yeongyu
Copy link
Owner Author

Cubic Review Round 5 Addressed

Fixed all 9 issues:

P1 Fixes

  1. plugin.ts - Close additionalServerHandle after successful account auth
  2. fetch.ts - Cancel response body on 429/5xx to prevent connection leaks

P2 Fixes

  1. plugin.ts - Close additionalServerHandle on OAuth error/missing code
  2. plugin.ts - Close additionalServerHandle on verifier mismatch
  3. auth.ts - Set activeIndex to -1 when all accounts removed
  4. storage.ts - Use shared getDataDir utility for consistent paths
  5. fetch.ts - Catch loadAccounts() IO errors with graceful fallback
  6. storage.test.ts - Improve test assertions with proper error tracking

All 75 auth tests pass ✅

@cubic-dev-ai please re-review

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Jan 8, 2026

Cubic Review Round 5 Addressed

Fixed all 9 issues:

P1 Fixes

...

@code-yeongyu I have started the AI code review. It will take a few minutes to complete.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 5 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/auth/antigravity/storage.ts">

<violation number="1" location="src/auth/antigravity/storage.ts:7">
P1: Breaking change on Windows: The shared `getDataDir()` uses `~/.local/share` on ALL platforms including Windows, but the old implementation used `%APPDATA%` on Windows. This will cause existing Windows users' account data to become inaccessible as the new code looks in a different location.

Either restore the Windows-specific handling in this file, or update the shared module to respect Windows conventions.</violation>
</file>

<file name="src/auth/antigravity/storage.test.ts">

<violation number="1" location="src/auth/antigravity/storage.test.ts:381">
P2: Test passes without any assertion when `thrownError` is null. Add an assertion about the expected behavior when permissions don't work (e.g., `expect(result).toBeNull()` or use a proper test skip mechanism), otherwise this test provides false confidence.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

import { getDataDir as getSharedDataDir } from "../../shared/data-path"

export function getDataDir(): string {
return join(getSharedDataDir(), "opencode")
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P1: Breaking change on Windows: The shared getDataDir() uses ~/.local/share on ALL platforms including Windows, but the old implementation used %APPDATA% on Windows. This will cause existing Windows users' account data to become inaccessible as the new code looks in a different location.

Either restore the Windows-specific handling in this file, or update the shared module to respect Windows conventions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/storage.ts, line 7:

<comment>Breaking change on Windows: The shared `getDataDir()` uses `~/.local/share` on ALL platforms including Windows, but the old implementation used `%APPDATA%` on Windows. This will cause existing Windows users' account data to become inaccessible as the new code looks in a different location.

Either restore the Windows-specific handling in this file, or update the shared module to respect Windows conventions.</comment>

<file context>
@@ -1,17 +1,10 @@
-
-  const xdgData = process.env.XDG_DATA_HOME || join(homedir(), ".local", "share")
-  return join(xdgData, "opencode")
+  return join(getSharedDataDir(), "opencode")
 }
 
</file context>
Fix with Cubic

if (thrownError) {
expect((thrownError as NodeJS.ErrnoException).code).not.toBe("ENOENT")
} else {
console.log("[TEST SKIP] File permissions did not work as expected on this system, got result:", result)
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P2: Test passes without any assertion when thrownError is null. Add an assertion about the expected behavior when permissions don't work (e.g., expect(result).toBeNull() or use a proper test skip mechanism), otherwise this test provides false confidence.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/storage.test.ts, line 381:

<comment>Test passes without any assertion when `thrownError` is null. Add an assertion about the expected behavior when permissions don't work (e.g., `expect(result).toBeNull()` or use a proper test skip mechanism), otherwise this test provides false confidence.</comment>

<file context>
@@ -362,13 +365,20 @@ describe("storage", () => {
+      if (thrownError) {
+        expect((thrownError as NodeJS.ErrnoException).code).not.toBe("ENOENT")
+      } else {
+        console.log("[TEST SKIP] File permissions did not work as expected on this system, got result:", result)
       }
 
</file context>
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

9 issues found across 12 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/auth/antigravity/constants.ts">

<violation number="1" location="src/auth/antigravity/constants.ts:143">
P3: Comment on `levels` is inconsistent with the actual supported values in `ANTIGRAVITY_MODEL_CONFIGS` (e.g., includes "minimal" and "medium"). This is misleading for future maintainers—update the comment (or restrict the values) so it matches real usage.</violation>

<violation number="2" location="src/auth/antigravity/constants.ts:234">
P2: Regex patterns for stripping UI variant suffixes are incomplete. They don't match all reasoning effort levels from `REASONING_EFFORT_BUDGET_MAP` (missing `minimal`, `medium`, `xhigh`), which could cause config lookup failures for models with these suffixes.</violation>
</file>

<file name="src/auth/antigravity/browser.test.ts">

<violation number="1" location="src/auth/antigravity/browser.test.ts:13">
P1: Assertion is too weak and test setup doesn't guarantee an error. Test should mock `open` to throw and assert `expect(result).toBe(false)`.</violation>

<violation number="2" location="src/auth/antigravity/browser.test.ts:13">
P1: Assertion is too weak - test name says 'returns true' but only verifies result is a boolean. This test will pass even if the function incorrectly returns `false`.</violation>
</file>

<file name="src/auth/antigravity/thinking.ts">

<violation number="1" location="src/auth/antigravity/thinking.ts:555">
P1: The `auto` reasoning_effort returns `-1` as `thinkingBudget` without implementing dynamic calculation. Per constants.ts, `auto: -1` is documented as triggering "dynamic calculation based on context", but no such logic exists here. This passes -1 directly to the API as the budget value.</violation>

<violation number="2" location="src/auth/antigravity/thinking.ts:689">
P1: `budgetToLevel` can return invalid levels for models. The function returns levels from `budgetMap` (e.g., "minimal", "medium") without validating they exist in `config.levels`. For `gemini-3-pro-preview` which only supports `["low", "high"]`, passing budget 512 returns "minimal" - an invalid level for that model.</violation>
</file>

<file name="src/auth/antigravity/request.test.ts">

<violation number="1" location="src/auth/antigravity/request.test.ts:177">
P2: Test has no assertions. While the comment says "should not throw", this is verified implicitly. The test should also verify the request field remains unchanged to ensure the function handles this edge case correctly.</violation>
</file>

<file name="src/auth/antigravity/integration.test.ts">

<violation number="1" location="src/auth/antigravity/integration.test.ts:169">
P2: Test does not match its description. The test claims to verify "System prompt not duplicated on retry" but only performs a single transformation - there is no retry scenario being tested. To actually test retry behavior, the test should call `transformRequest` (and/or apply thinking config) twice and verify the identity block count remains 1 after the second pass.</violation>
</file>

<file name="src/auth/antigravity/request.ts">

<violation number="1" location="src/auth/antigravity/request.ts:170">
P3: Misleading comment: says "Prepend existing parts" but the code actually appends them using `push()`. The Antigravity prompt comes first, then existing parts are added after - this is appending, not prepending.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

const result = await openBrowserURL(url)

// #then
expect(typeof result).toBe("boolean")
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P1: Assertion is too weak and test setup doesn't guarantee an error. Test should mock open to throw and assert expect(result).toBe(false).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/browser.test.ts, line 13:

<comment>Assertion is too weak and test setup doesn't guarantee an error. Test should mock `open` to throw and assert `expect(result).toBe(false)`.</comment>

<file context>
@@ -0,0 +1,37 @@
+    const result = await openBrowserURL(url)
+
+    // #then
+    expect(typeof result).toBe("boolean")
+  })
+
</file context>
Fix with Cubic

const result = await openBrowserURL(url)

// #then
expect(typeof result).toBe("boolean")
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P1: Assertion is too weak - test name says 'returns true' but only verifies result is a boolean. This test will pass even if the function incorrectly returns false.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/browser.test.ts, line 13:

<comment>Assertion is too weak - test name says 'returns true' but only verifies result is a boolean. This test will pass even if the function incorrectly returns `false`.</comment>

<file context>
@@ -0,0 +1,37 @@
+    const result = await openBrowserURL(url)
+
+    // #then
+    expect(typeof result).toBe("boolean")
+  })
+
</file context>
Suggested change
expect(typeof result).toBe("boolean")
expect(result).toBe(true)
Fix with Cubic

// Special marker: delete thinkingConfig entirely
return { deleteThinkingConfig: true }
}
return {
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P1: The auto reasoning_effort returns -1 as thinkingBudget without implementing dynamic calculation. Per constants.ts, auto: -1 is documented as triggering "dynamic calculation based on context", but no such logic exists here. This passes -1 directly to the API as the budget value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/thinking.ts, line 555:

<comment>The `auto` reasoning_effort returns `-1` as `thinkingBudget` without implementing dynamic calculation. Per constants.ts, `auto: -1` is documented as triggering "dynamic calculation based on context", but no such logic exists here. This passes -1 directly to the API as the budget value.</comment>

<file context>
@@ -535,6 +543,22 @@ export function extractThinkingConfig(
+        // Special marker: delete thinkingConfig entirely
+        return { deleteThinkingConfig: true }
+      }
+      return {
+        includeThoughts: true,
+        thinkingBudget: budget,
</file context>
Suggested change
return {
if (budget === -1) {
// Auto mode: use default budget (dynamic calculation not yet implemented)
return {
includeThoughts: true,
thinkingBudget: DEFAULT_THINKING_BUDGET,
}
}
return {
Fix with Cubic

}

// Return matching level or highest available
if (budgetMap[budget]) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P1: budgetToLevel can return invalid levels for models. The function returns levels from budgetMap (e.g., "minimal", "medium") without validating they exist in config.levels. For gemini-3-pro-preview which only supports ["low", "high"], passing budget 512 returns "minimal" - an invalid level for that model.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/thinking.ts, line 689:

<comment>`budgetToLevel` can return invalid levels for models. The function returns levels from `budgetMap` (e.g., "minimal", "medium") without validating they exist in `config.levels`. For `gemini-3-pro-preview` which only supports `["low", "high"]`, passing budget 512 returns "minimal" - an invalid level for that model.</comment>

<file context>
@@ -569,3 +593,163 @@ export function resolveThinkingConfig(
+  }
+
+  // Return matching level or highest available
+  if (budgetMap[budget]) {
+    return budgetMap[budget]
+  }
</file context>
Suggested change
if (budgetMap[budget]) {
if (budgetMap[budget] && config.levels.includes(budgetMap[budget])) {
Fix with Cubic

// #when
injectSystemPrompt(wrappedBody)

// #then - should not throw
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P2: Test has no assertions. While the comment says "should not throw", this is verified implicitly. The test should also verify the request field remains unchanged to ensure the function handles this edge case correctly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/request.test.ts, line 177:

<comment>Test has no assertions. While the comment says "should not throw", this is verified implicitly. The test should also verify the request field remains unchanged to ensure the function handles this edge case correctly.</comment>

<file context>
@@ -0,0 +1,224 @@
+      // #when
+      injectSystemPrompt(wrappedBody)
+
+      // #then - should not throw
+    })
+  })
</file context>
Suggested change
// #then - should not throw
// #then - should not throw, request should remain unchanged
expect(wrappedBody.request).toBe("not an object")
Fix with Cubic

expect(thinkingConfigResult?.thinkingBudget).toBe(24576)
})

it("System prompt not duplicated on retry", () => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P2: Test does not match its description. The test claims to verify "System prompt not duplicated on retry" but only performs a single transformation - there is no retry scenario being tested. To actually test retry behavior, the test should call transformRequest (and/or apply thinking config) twice and verify the identity block count remains 1 after the second pass.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/integration.test.ts, line 169:

<comment>Test does not match its description. The test claims to verify "System prompt not duplicated on retry" but only performs a single transformation - there is no retry scenario being tested. To actually test retry behavior, the test should call `transformRequest` (and/or apply thinking config) twice and verify the identity block count remains 1 after the second pass.</comment>

<file context>
@@ -0,0 +1,306 @@
+      expect(thinkingConfigResult?.thinkingBudget).toBe(24576)
+    })
+
+    it("System prompt not duplicated on retry", () => {
+      // #given
+      const inputBody: Record<string, unknown> = {
</file context>
Fix with Cubic

Comment on lines +234 to +235
normalized = normalized.replace(/-thinking-(low|medium|high)$/, "")
normalized = normalized.replace(/-(high|low)$/, "")
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P2: Regex patterns for stripping UI variant suffixes are incomplete. They don't match all reasoning effort levels from REASONING_EFFORT_BUDGET_MAP (missing minimal, medium, xhigh), which could cause config lookup failures for models with these suffixes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/constants.ts, line 234:

<comment>Regex patterns for stripping UI variant suffixes are incomplete. They don't match all reasoning effort levels from `REASONING_EFFORT_BUDGET_MAP` (missing `minimal`, `medium`, `xhigh`), which could cause config lookup failures for models with these suffixes.</comment>

<file context>
@@ -72,3 +72,167 @@ export const ANTIGRAVITY_TOKEN_REFRESH_BUFFER_MS = 60_000
+  }
+
+  // 3. Strip UI variant suffixes (-high, -low, -thinking-*)
+  normalized = normalized.replace(/-thinking-(low|medium|high)$/, "")
+  normalized = normalized.replace(/-(high|low)$/, "")
+
</file context>
Suggested change
normalized = normalized.replace(/-thinking-(low|medium|high)$/, "")
normalized = normalized.replace(/-(high|low)$/, "")
normalized = normalized.replace(/-thinking-(minimal|low|medium|high|xhigh)$/, "")
normalized = normalized.replace(/-(minimal|low|medium|high|xhigh)$/, "")
Fix with Cubic

min: number
max: number
zeroAllowed: boolean
levels?: string[] // lowercase only: "low", "high" (NOT "LOW", "HIGH")
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P3: Comment on levels is inconsistent with the actual supported values in ANTIGRAVITY_MODEL_CONFIGS (e.g., includes "minimal" and "medium"). This is misleading for future maintainers—update the comment (or restrict the values) so it matches real usage.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/constants.ts, line 143:

<comment>Comment on `levels` is inconsistent with the actual supported values in `ANTIGRAVITY_MODEL_CONFIGS` (e.g., includes "minimal" and "medium"). This is misleading for future maintainers—update the comment (or restrict the values) so it matches real usage.</comment>

<file context>
@@ -72,3 +72,167 @@ export const ANTIGRAVITY_TOKEN_REFRESH_BUFFER_MS = 60_000
+  min: number
+  max: number
+  zeroAllowed: boolean
+  levels?: string[] // lowercase only: "low", "high" (NOT "LOW", "HIGH")
+}
+
</file context>
Suggested change
levels?: string[] // lowercase only: "low", "high" (NOT "LOW", "HIGH")
levels?: string[] // lowercase only (e.g., "minimal", "low", "medium", "high")
Fix with Cubic

// Build new parts array - Antigravity prompt first, then existing parts
const newParts: Array<{ text: string }> = [{ text: ANTIGRAVITY_SYSTEM_PROMPT }]

// Prepend existing parts if systemInstruction exists with parts
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P3: Misleading comment: says "Prepend existing parts" but the code actually appends them using push(). The Antigravity prompt comes first, then existing parts are added after - this is appending, not prepending.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/request.ts, line 170:

<comment>Misleading comment: says "Prepend existing parts" but the code actually appends them using `push()`. The Antigravity prompt comes first, then existing parts are added after - this is appending, not prepending.</comment>

<file context>
@@ -133,6 +134,58 @@ function generateRequestId(): string {
+  // Build new parts array - Antigravity prompt first, then existing parts
+  const newParts: Array<{ text: string }> = [{ text: ANTIGRAVITY_SYSTEM_PROMPT }]
+
+  // Prepend existing parts if systemInstruction exists with parts
+  if (req.systemInstruction && typeof req.systemInstruction === "object") {
+    const existing = req.systemInstruction as Record<string, unknown>
</file context>
Suggested change
// Prepend existing parts if systemInstruction exists with parts
// Append existing parts if systemInstruction exists with parts
Fix with Cubic

Root cause: Antigravity API expects 'claude-sonnet-4-5-thinking' but we were
sending 'gemini-claude-sonnet-4-5-thinking'. Ported alias mapping from
CLIProxyAPI antigravity_executor.go:1328-1347.

Transforms:
- gemini-claude-sonnet-4-5-thinking → claude-sonnet-4-5-thinking
- gemini-claude-opus-4-5-thinking → claude-opus-4-5-thinking
- gemini-3-pro-preview → gemini-3-pro-high
- gemini-3-flash-preview → gemini-3-flash
Missing required fields from CLIProxyAPI implementation:
- requestType: 'agent'
- request.toolConfig.functionCallingConfig.mode: 'VALIDATED'
- Delete request.safetySettings

Also strip 'antigravity-' prefix before alias transformation.
CLIProxyAPI's alias mappings don't work with public Antigravity API:
- gemini-3-pro-preview → gemini-3-pro-high (404!)
- gemini-3-flash-preview → gemini-3-flash (404!)

Tested: -preview suffix names work, transformed names return 404.
Keep only gemini-claude-* prefix stripping for future Claude support.
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/auth/antigravity/request.ts">

<violation number="1" location="src/auth/antigravity/request.ts:211">
P2: The `functionCallingConfig` object is completely replaced rather than merged, which could lose existing properties like `allowedFunctionNames`. Consider merging the nested object to preserve any user-specified function restrictions.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

…gravity API

Implements explicit switch-based model name mappings for Antigravity API.
Updates SANDBOX endpoint constants to clarify quota/availability behavior.
Fixes test expectations to match new transformation logic.

🤖 Generated with assistance of OhMyOpenCode
@code-yeongyu code-yeongyu force-pushed the feat/multi-account-antigravity-auth branch from deb45fb to 07f0233 Compare January 8, 2026 13:35
@code-yeongyu code-yeongyu merged commit 8394926 into dev Jan 8, 2026
3 checks passed
@code-yeongyu code-yeongyu deleted the feat/multi-account-antigravity-auth branch January 8, 2026 13:37
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/auth/antigravity/constants.ts">

<violation number="1" location="src/auth/antigravity/constants.ts:39">
P3: The comment is inconsistent with the actual fallback list (non-sandbox endpoints are included). Either update the comment to reflect mixed endpoints or split Claude vs non-Claude endpoints so the behavior is explicit.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


// API Endpoint Fallbacks (order: daily → autopush → prod)
// API Endpoint Fallbacks - matches CLIProxyAPI antigravity_executor.go:1192-1201
// Claude models only available on SANDBOX endpoints (429 quota vs 404 not found)
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

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

P3: The comment is inconsistent with the actual fallback list (non-sandbox endpoints are included). Either update the comment to reflect mixed endpoints or split Claude vs non-Claude endpoints so the behavior is explicit.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/auth/antigravity/constants.ts, line 39:

<comment>The comment is inconsistent with the actual fallback list (non-sandbox endpoints are included). Either update the comment to reflect mixed endpoints or split Claude vs non-Claude endpoints so the behavior is explicit.</comment>

<file context>
@@ -35,11 +35,12 @@ export const ANTIGRAVITY_SCOPES = [
 
-// API Endpoint Fallbacks (order: daily → autopush → prod)
+// API Endpoint Fallbacks - matches CLIProxyAPI antigravity_executor.go:1192-1201
+// Claude models only available on SANDBOX endpoints (429 quota vs 404 not found)
 export const ANTIGRAVITY_ENDPOINT_FALLBACKS = [
-  "https://daily-cloudcode-pa.sandbox.googleapis.com", // dev
</file context>
Suggested change
// Claude models only available on SANDBOX endpoints (429 quota vs 404 not found)
// Claude models may require SANDBOX endpoints (429 quota vs 404 not found on some prod endpoints).
Fix with Cubic

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.

4 participants