fix: Handle 403 VALIDATION_REQUIRED error and prevent "Body already used"#356
fix: Handle 403 VALIDATION_REQUIRED error and prevent "Body already used"#356d-init-d wants to merge 2 commits intoNoeFabris:devfrom
Conversation
Add support for tracking accounts that require Google verification. When Google returns a 403 PERMISSION_DENIED with VALIDATION_REQUIRED reason, the account will be marked with this cooldown reason so users know they need to verify their account at accounts.google.com. Related to issues NoeFabris#348, NoeFabris#354
WalkthroughThis PR updates Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile OverviewGreptile SummaryThis PR successfully implements handling for Google's 403 VALIDATION_REQUIRED error by adding account cooldown tracking and automatic account switching. The implementation prevents "Body already used" errors by breaking out of the endpoint loop before the response body is consumed multiple times. Key changes:
Implementation correctness:
Minor optimization:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant Plugin as Antigravity Plugin
participant Google as Google API
participant AM as Account Manager
participant UI as Toast/Debug
Client->>Plugin: Make API Request
Plugin->>Google: POST with Account Credentials
Google-->>Plugin: 403 PERMISSION_DENIED (VALIDATION_REQUIRED)
Plugin->>Plugin: Clone response
Plugin->>Plugin: extractRetryInfoFromBody(cloned)
Plugin->>Plugin: Parse error details
alt reason === "VALIDATION_REQUIRED"
Plugin->>AM: markAccountCoolingDown(10min, "validation-required")
Plugin->>AM: markRateLimited(10min)
Plugin->>AM: recordFailure(account)
Plugin->>UI: showToast("Account requires verification...")
Plugin->>UI: pushDebug("validation-required")
Plugin->>Plugin: Set shouldSwitchAccount = true
Plugin->>Plugin: break (exit endpoint loop)
Plugin->>AM: Select next available account
Plugin->>Google: Retry with different account
else Other 403 errors
Plugin->>Plugin: Continue to shouldRetryEndpoint logic
Plugin->>Plugin: logResponseBody()
Plugin->>Plugin: Retry with endpoint fallback
end
|
| * - "project-error": Project ID resolution failed | ||
| * - "validation-required": Google requires account verification (403 PERMISSION_DENIED with VALIDATION_REQUIRED) | ||
| */ | ||
| export type CooldownReason = "auth-failure" | "network-error" | "project-error" | "validation-required"; |
There was a problem hiding this comment.
The validation-required cooldown reason is added here, but the actual implementation in src/plugin.ts that handles 403 VALIDATION_REQUIRED errors is missing from this PR. According to the PR description, code needs to be added before line 1616 in plugin.ts to:
- Clone the 403 response
- Check for VALIDATION_REQUIRED reason
- Mark the account as cooling down
- Switch to another account
Without the plugin.ts changes, this type addition has no effect.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugin/storage.ts
Line: 183:183
Comment:
The `validation-required` cooldown reason is added here, but the actual implementation in `src/plugin.ts` that handles 403 VALIDATION_REQUIRED errors is missing from this PR. According to the PR description, code needs to be added before line 1616 in `plugin.ts` to:
1. Clone the 403 response
2. Check for VALIDATION_REQUIRED reason
3. Mark the account as cooling down
4. Switch to another account
Without the `plugin.ts` changes, this type addition has no effect.
How can I resolve this? If you propose a fix, please make it concise.| /** Per-account device fingerprint for rate limit mitigation */ | ||
| fingerprint?: import("./fingerprint").Fingerprint; | ||
| /** History of previous fingerprints for this account */ | ||
| fingerprintHistory?: import("./fingerprint").FingerprintVersion[]; |
There was a problem hiding this comment.
Verify that the FingerprintVersion type is exported from src/plugin/fingerprint.ts and is being used correctly by the fingerprintHistory consumers in src/plugin/accounts.ts. The type import looks correct, but verify that all existing fingerprint history code is compatible with this schema addition.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugin/storage.ts
Line: 200:200
Comment:
Verify that the `FingerprintVersion` type is exported from `src/plugin/fingerprint.ts` and is being used correctly by the `fingerprintHistory` consumers in `src/plugin/accounts.ts`. The type import looks correct, but verify that all existing fingerprint history code is compatible with this schema addition.
How can I resolve this? If you propose a fix, please make it concise.- Detect 403 responses with VALIDATION_REQUIRED reason from Google API - Clone response before parsing to prevent 'Body already used' SDK errors - Put affected account on 10-minute cooldown with 'validation-required' reason - Show warning toast directing user to accounts.google.com for verification - Auto-switch to next available account for seamless continuation - Track failure in health tracker for account reliability scoring
| const cloned403 = response.clone(); | ||
| const body403 = await extractRetryInfoFromBody(cloned403); |
There was a problem hiding this comment.
Redundant clone - extractRetryInfoFromBody already clones the response internally (line 538). Pass response directly.
| const cloned403 = response.clone(); | |
| const body403 = await extractRetryInfoFromBody(cloned403); | |
| const body403 = await extractRetryInfoFromBody(response); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugin.ts
Line: 1614:1615
Comment:
Redundant clone - `extractRetryInfoFromBody` already clones the response internally (line 538). Pass `response` directly.
```suggestion
const body403 = await extractRetryInfoFromBody(response);
```
How can I resolve this? If you propose a fix, please make it concise.|
could you check the merge issue @d-init-d ? |
Review Package (RP) — PR #356Repository: NoeFabris/opencode-antigravity-auth 1. Executive SummaryThis PR addresses a critical edge case where Google returns 403 PERMISSION_DENIED with VALIDATION_REQUIRED reason, requiring account verification. The fix:
Impact: Users with multiple accounts will now gracefully switch to healthy accounts when one requires verification, instead of hitting cryptic SDK errors. 2. Scope / Changes
Key Implementation Details// 403 handler logic (simplified)
if (response.status === 403) {
const cloned403 = response.clone();
const body403 = await extractRetryInfoFromBody(cloned403);
if (body403.reason === "VALIDATION_REQUIRED") {
const cooldownMs = 10 * 60 * 1000;
accountManager.markAccountCoolingDown(account, cooldownMs, "validation-required");
accountManager.markRateLimited(account, cooldownMs, family, headerStyle, model);
// ... toast + force switch
}
}3. Strengths
4. Issues / Risks
|
| ID | Action Item | Owner | Acceptance Criteria | Priority |
|---|---|---|---|---|
| T1 | Extend extractRateLimitBodyInfo to parse reason without @type |
@d-init-d | VALIDATION_REQUIRED detected in both {@type, reason} and {reason} formats |
🔴 High |
| T2 | Add unit test for 403 reason parsing | @d-init-d | Test cases: (a) ErrorInfo with reason, (b) plain reason, (c) no reason | 🔴 High |
| T3 | Verify cooldown wait time calculation | @d-init-d | When all accounts coolingDown, waitMs reflects actual cooldown (10min) not fallback (60s) | 🟡 Medium |
| T4 | Remove redundant response.clone() in 403 handler | @d-init-d | 403 handler passes response directly; tests pass | 🟢 Low |
| T5 | Add integration test for account switch on VALIDATION_REQUIRED | @d-init-d | Mock 403 response triggers account switch; second account used | 🟡 Medium |
| T6 | Update CHANGELOG.md | @d-init-d | Entry describes 403 handling improvement | 🟢 Low |
| T7 | Document new cooldown reason | @d-init-d | JSDoc updated, user docs mention validation-required handling | 🟢 Low |
8. Testing Plan
Unit Tests (Required)
describe("extractRateLimitBodyInfo", () => {
it("should parse VALIDATION_REQUIRED with google.rpc.ErrorInfo", () => {
const body = {
error: {
details: [{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
reason: "VALIDATION_REQUIRED"
}]
}
};
expect(extractRateLimitBodyInfo(body).reason).toBe("VALIDATION_REQUIRED");
});
it("should parse VALIDATION_REQUIRED without @type field", () => {
const body = {
error: {
details: [{ reason: "VALIDATION_REQUIRED" }]
}
};
expect(extractRateLimitBodyInfo(body).reason).toBe("VALIDATION_REQUIRED");
});
it("should return undefined reason when not present", () => {
const body = { error: { message: "Some error" } };
expect(extractRateLimitBodyInfo(body).reason).toBeUndefined();
});
});Integration Tests (Recommended)
-
Multi-Account Switch Test:
- Setup: 2 accounts configured
- Trigger: Account 1 returns 403 VALIDATION_REQUIRED
- Expected: Account 2 is used for subsequent request
-
Cooldown Wait Test:
- Setup: All accounts in validation-required cooldown (10min)
- Trigger: Request made
- Expected: Wait time calculated as ~10min, not 60s fallback
-
Body Clone Test:
- Setup: Mock 403 response
- Trigger: Handler processes response
- Expected: Response body readable after handler (no "already used" error)
Manual Testing Checklist
- Test with actual Google account requiring verification
- Verify toast message displays correctly
- Confirm account switches to next available account
- Check that cooldown prevents immediate retry of same account
- Verify debug logs show
validation-requiredevent
9. Summary & Next Steps
This PR is well-structured and addresses a real user pain point. The core logic is sound, but Fix 1 (reason parsing) should be addressed before merge to ensure the fix works for all Google response formats.
Recommended Actions:
- Before Merge: Complete T1, T2 (reason parsing fix + tests)
- After Merge: Complete T3, T4, T5 (optimization + integration tests)
- Future: Consider T6, T7 (documentation)
LGTM with Fix 1 implemented. 🚀
Review Package prepared for PR #356 | opencode-antigravity-auth
wtf |
Summary
This PR fixes the "Failed to process error response: Body already used" error that occurs when Google returns a 403 PERMISSION_DENIED with VALIDATION_REQUIRED reason.
Problem
When Google requires account verification, it returns:
{ "error": { "code": 403, "message": "To continue, verify your account at https://accounts.google.com/signin/continue?...", "status": "PERMISSION_DENIED", "details": [{"reason": "VALIDATION_REQUIRED"}] } }Currently, this 403 error is treated like other 403 errors (endpoint fallback), but:
Solution
Added
"validation-required"toCooldownReasontype instorage.tsEnhanced 403 handling in
plugin.ts(to be applied):VALIDATION_REQUIREDreason in 403 responsesCode Changes Required for
plugin.tsAdd the following code before line 1616 (before
const shouldRetryEndpoint):Testing
Related Issues
and these duplicate issues:
Breaking Changes
None - this is a backward-compatible enhancement.
Screenshots
N/A - this is a backend error handling improvement.