Skip to content

fix(providers): mark Antigravity connects with no Cloud Code projectId as degraded (#11284) - #11358

Merged
diegosouzapw merged 3 commits into
cheaper-inference:release/v3.8.50from
rqzbeh:fix/11284-antigravity-degraded-connection
Aug 24, 2026
Merged

diegosouzapw merged 3 commits into
cheaper-inference:release/v3.8.50from
rqzbeh:fix/11284-antigravity-degraded-connection

Conversation

@rqzbeh

@rqzbeh rqzbeh commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #11284, following the maintainer's suggested direction: Antigravity OAuth connects that end without a Cloud Code projectId are now persisted as testStatus:"degraded" instead of a silent testStatus:"active" row that showed "Connected" while every model call failed.

Root cause

Production evidence (VPS docker instance, read-only inspection): 5 of 18 stored antigravity rows had project_id="" with providerSpecificData.projectId="" while tier=g1-pro-tier / subscriptionTier="Google AI Pro" were fully populated — the token exchange and loadCodeAssist round-trips succeeded, but Google returned no cloudaicompanionProject. These are Google BYOP accounts (no automatic GCP project creation for standard-tier accounts, #8491): onboardUser answers 200 OK without creating a project. The old connect-time path swallowed that outcome (bare catch / .catch(() => {})) and persisted active-with-empty-projectId; request-time selection then excluded those rows → "No active credentials for provider: antigravity".

Changes

  1. Connect-time classification (src/lib/oauth/providers/antigravity.ts): postExchangeAntigravity now reports why no project was found —
    • requires_manual_project: onboardUser answered 200 without cloudaicompanionProject in the body (BYOP; retrying can never succeed);
    • discovery_failed: loadCodeAssist/onboardUser errored, timed out, or stayed empty after onboarding.
      A projectId present directly in the onboardUser response is used when discovery lags behind server-side project creation.
  2. Degrade-marking at connect (src/lib/oauth/antigravityProjectGate.ts + [action]/route.ts exchange/poll-callback + connectionPersistence.ts): the connection IS saved, but with testStatus:"degraded", errorCode:"missing_project_id", lastErrorType:"oauth_missing_project_id" and a descriptive lastError. Both upsert branches (update-existing and create-new) apply the degrade fields. The response stays success:true and carries a warning string so the dashboard can explain the state instead of showing a false green. No rejection, no data loss — the refresh token stays stored.
  3. Self-healing: persistDiscoveredAntigravityProjectId() flips the row back to active the moment a projectId is discovered at request time (transient failures recover with zero user action). Confirmed-BYOP accounts get disabled by markAntigravityMissingCloudCodeProject() on first dispatch (isActive:false + unavailable — recoverable, not terminal) so selection rotates to healthy siblings.
  4. Selection-side filter (open-sse/services/antigravityProjectPersist.ts): empty-projectId siblings and confirmed-missing rows (even with stale ids) are skipped when a healthier account exists. When NO account has a project, the pool passes through unchanged so the typed runtime 422 (not an empty-selection 404) explains what to fix.

User-facing flow after this PR

  • Transient discovery failure: connect completes with a visible degraded/warning state → first chat request re-runs bootstrap → row self-heals to active.
  • BYOP account: connect completes as degraded → first request returns the actionable GCP_PROJECT_REQUIRED 422 → account auto-disabled so siblings serve traffic. Dashboard shows the real error instead of "Connected".

TDD validation (Hard Rule #18)

New tests (RED→GREEN):

  • tests/unit/antigravity-oauth-empty-project-rejection.test.ts — outcome classification + mapTokens propagation
  • tests/unit/oauth-route-antigravity-project-gate.test.ts — degrade gate wired into both exchange branches; saved-not-active contract; typed error fields
  • tests/unit/antigravity-missing-project-autodisable.test.ts — disable on confirm / re-enable on discovery
  • tests/unit/antigravity-empty-project-selection.test.ts — selection rotation semantics

Updated: tests/unit/antigravity-missing-project-chat.test.ts (persisted-state assertions).

Focused suites: 51/51 new+regression tests pass (run via fleet worker); typecheck:core exit 0; check:file-size OK (route 979 < cap 1000); dashboard/open-sse typecheck baselines unchanged.

⚠️ base-red inherited: #9985

…tId (cheaper-inference#11284)

Google BYOP accounts (no automatic GCP project creation, cheaper-inference#8491) and
transient discovery failures both ended with postExchangeAntigravity
returning an empty projectId that the OAuth route persisted as a
testStatus:"active" connection — the dashboard showed "Connected"
while every model call failed.

- postExchange now classifies WHY discovery failed: BYOP answers 200
  without cloudaicompanionProject (requires_manual_project); upstream
  errors/timeouts/lag classify as discovery_failed. A project id found
  in the onboardUser body is used directly when loadCodeAssist lags.
- The exchange and poll-callback branches reject antigravity/agy
  connects carrying a failed outcome (422 missing_cloud_code_project)
  instead of persisting them.
- markAntigravityMissingCloudCodeProject disables confirmed-missing
  rows (isActive=false, testStatus=unavailable — recoverable, not
  terminal) so selection rotates to healthy siblings; a discovered
  projectId re-enables the account.
- preferAntigravityConnectionsWithStoredProject drops empty-projectId
  siblings when a healthier account exists (never empties the pool).
@rqzbeh
rqzbeh requested a review from diegosouzapw as a code owner August 24, 2026 06:53
check:file-size caps new files at 1000 LOC; the route grew to 1007 with
the inline gate. Move antigravityMissingProjectRejection() +
AntigravityProjectDiscoveryRejection into src/lib/oauth/antigravityProjectGate.ts
(route back to 987). Call sites build the typed 422 via NextResponse.json
with a satisfies check. Gate tests now read the extracted module.
@rqzbeh
rqzbeh marked this pull request as draft August 24, 2026 08:20
…projectId

Per maintainer direction on cheaper-inference#11284: save the connection with
testStatus:"degraded" + typed error markers (errorCode=missing_project_id,
lastErrorType=oauth_missing_project_id) instead of rejecting the connect.
The refresh token stays stored, so request-time bootstrap self-heals the
row via persistDiscoveredAntigravityProjectId; confirmed-BYOP accounts are
disabled on first dispatch by markAntigravityMissingCloudCodeProject.

- antigravityProjectGate.ts: rejection payload -> antigravityDegradedProjectState()
- route.ts exchange/poll-callback: apply degrade fields to update+create payloads,
  surface success:true with a warning field
- connectionPersistence.ts: buildOAuthConnectionCreatePayload accepts the degrade state
@rqzbeh
rqzbeh marked this pull request as ready for review August 24, 2026 08:52
@rqzbeh rqzbeh changed the title fix(providers): reject Antigravity connects with no Cloud Code projectId (#11284) fix(providers): mark Antigravity connects with no Cloud Code projectId as degraded (#11284) Aug 24, 2026
@diegosouzapw
diegosouzapw merged commit 6de542b into cheaper-inference:release/v3.8.50 Aug 24, 2026
19 of 24 checks passed
@rqzbeh
rqzbeh deleted the fix/11284-antigravity-degraded-connection branch August 24, 2026 20:13
diegosouzapw added a commit that referenced this pull request Aug 25, 2026
…, antigravity BYOP contract)

Two unrelated real reds on the release tip:

* fix(providers): flag MiniMax M3 as multimodal on the Volcengine Ark plans.
  d732cf6 ("feat(volcengine): add Ark plan providers") added
  volcengine-agent-plan/minimax-m3 and volcengine-coding-plan/minimax-m3
  without supportsVision, breaking the LEDGER-4 invariant that every
  minimax-m3 registry entry except PromptQL (text-only upstream) is flagged
  multimodal. Every other provider carrying the model (opencode-zen,
  opencode-go, bazaarlink, ollama-cloud, codebuddy-cn, trae) sets it.
  Registry metadata defect, not a stale test.

* test(antigravity): align the empty-projectId onboarding test to the
  contract shipped by #11284/#11358 (6de542b). That change made an
  onboardUser 200 whose body carries NO cloudaicompanionProject mean Google
  BYOP — no project was created and none ever will be — so it short-circuits
  before the retry loadCodeAssist. The older test still mocked onboardUser
  with the bare { done: true } BYOP shape while asserting the retry path, so
  it pinned a contract that was deliberately moved. The mock now returns a
  real onboarding-success body; every assertion is kept, and the id in the
  onboard body deliberately differs from the expected one so the test still
  proves the projectId came from the retry discovery.

Refs #11284
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…d as degraded (cheaper-inference#11284) (cheaper-inference#11358)

Merged via consolidated batch validation. Production evidence (VPS docker instance): Antigravity OAuth connects ending without a Cloud Code projectId were persisted as silently active while every model call failed; now persisted as degraded. Own tests pass.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…, antigravity BYOP contract)

Two unrelated real reds on the release tip:

* fix(providers): flag MiniMax M3 as multimodal on the Volcengine Ark plans.
  0a9cf03 ("feat(volcengine): add Ark plan providers") added
  volcengine-agent-plan/minimax-m3 and volcengine-coding-plan/minimax-m3
  without supportsVision, breaking the LEDGER-4 invariant that every
  minimax-m3 registry entry except PromptQL (text-only upstream) is flagged
  multimodal. Every other provider carrying the model (opencode-zen,
  opencode-go, bazaarlink, ollama-cloud, codebuddy-cn, trae) sets it.
  Registry metadata defect, not a stale test.

* test(antigravity): align the empty-projectId onboarding test to the
  contract shipped by cheaper-inference#11284/cheaper-inference#11358 (a554154). That change made an
  onboardUser 200 whose body carries NO cloudaicompanionProject mean Google
  BYOP — no project was created and none ever will be — so it short-circuits
  before the retry loadCodeAssist. The older test still mocked onboardUser
  with the bare { done: true } BYOP shape while asserting the retry path, so
  it pinned a contract that was deliberately moved. The mock now returns a
  real onboarding-success body; every assertion is kept, and the id in the
  onboard body deliberately differs from the expected one so the test still
  proves the projectId came from the retry discovery.

Refs cheaper-inference#11284
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.

Manual OAuth callback entry for Antigravity "connects" but leaves no working credential — 404 No active credentials on retest

2 participants