{"record":{"id":"dcf4110b8152b6d5","repo":"router-for-me/CLIProxyAPI","slug":"antigravity-token-exchange-decode-response-w","errorCode":null,"errorMessage":"antigravity token exchange: decode response: %w","messagePattern":"antigravity token exchange: decode response: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/antigravity/auth.go","lineNumber":176,"sourceCode":"\t\t\tlog.Errorf(\"antigravity token exchange: close body error: %v\", errClose)\n\t\t}\n\t}()\n\n\tif resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {\n\t\tbodyBytes, errRead := io.ReadAll(io.LimitReader(resp.Body, 8<<10))\n\t\tif errRead != nil {\n\t\t\treturn nil, fmt.Errorf(\"antigravity token exchange: read response: %w\", errRead)\n\t\t}\n\t\tbody := strings.TrimSpace(string(bodyBytes))\n\t\tif body == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"antigravity token exchange: request failed: status %d\", resp.StatusCode)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"antigravity token exchange: request failed: status %d: %s\", resp.StatusCode, body)\n\t}\n\n\tvar token TokenResponse\n\tif errDecode := json.NewDecoder(resp.Body).Decode(&token); errDecode != nil {\n\t\treturn nil, fmt.Errorf(\"antigravity token exchange: decode response: %w\", errDecode)\n\t}\n\treturn &token, nil\n}\n\n// FetchUserInfo retrieves user email from Google\nfunc (o *AntigravityAuth) FetchUserInfo(ctx context.Context, accessToken string) (string, error) {\n\taccessToken = strings.TrimSpace(accessToken)\n\tif accessToken == \"\" {\n\t\treturn \"\", fmt.Errorf(\"antigravity userinfo: missing access token\")\n\t}\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, UserInfoEndpoint, nil)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"antigravity userinfo: create request: %w\", err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+accessToken)\n\treq.Header.Set(\"User-Agent\", o.shortUserAgent())\n\n\tresp, errDo := o.httpClient.Do(req)","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/antigravity/auth.go#L158-L194","documentation":"The token exchange returned 2xx but the response body is not decodable as a TokenResponse JSON (access_token/refresh_token fields). Causes: empty body despite 200, HTML/HTML-fragment body, or a schema where the token fields are nested differently.","triggerScenarios":"A proxy returns 200 with an auth-page body; upstream changes token response shape; body truncated by a read timeout mid-decode.","commonSituations":"Captive portals / proxy interception on the token endpoint; partial responses on flaky links.","solutions":["Log the raw body (redacting tokens) when decode fails to see what was actually returned","If a proxy intercepted the response, bypass it or trust its CA and retry with a fresh code","If the schema changed, update TokenResponse fields/json tags"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"var typeErr *json.UnmarshalTypeError\nif errors.As(err, &typeErr) {\n\t// schema drift: token JSON shape changed\n} else if strings.Contains(err.Error(), \"decode response\") {\n\t// interception or truncation — retry with a fresh code\n}","preventionTips":["Verify Content-Type on 2xx token responses","Detect and bypass proxies that rewrite OAuth responses"],"tags":["oauth","antigravity","json","response-body"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}