{"record":{"id":"77ac688656c639e9","repo":"router-for-me/CLIProxyAPI","slug":"antigravity-token-exchange-read-response-w","errorCode":null,"errorMessage":"antigravity token exchange: read response: %w","messagePattern":"antigravity token exchange: read response: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/antigravity/auth.go","lineNumber":165,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"antigravity token exchange: create request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\n\tresp, errDo := o.httpClient.Do(req)\n\tif errDo != nil {\n\t\treturn nil, fmt.Errorf(\"antigravity token exchange: execute request: %w\", errDo)\n\t}\n\tdefer func() {\n\t\tif errClose := resp.Body.Close(); errClose != nil {\n\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)","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/antigravity/auth.go#L147-L183","documentation":"The token exchange returned a non-2xx status and reading the (limited to 8 KiB) error response body failed. The server responded with an error status but the body read was interrupted — connection reset mid-body, or the context was cancelled while reading.","triggerScenarios":"Upstream returns 4xx/5xx and closes the connection before the body completes; context deadline expires between response headers and body read; proxy truncates the error body.","commonSituations":"Aggressive per-request timeouts set just above the header round-trip; flaky networks; rate-limiting endpoints that RST connections.","solutions":["Increase the context/httpClient timeout so body reads are not cut off after headers arrive","Retry the request; transient resets usually succeed on retry (but note the auth code single-use constraint)","If persistent, capture resp.StatusCode in the error to know what the server was trying to report"],"exampleFix":"// before\nif errRead != nil {\n\treturn nil, fmt.Errorf(\"antigravity token exchange: read response: %w\", errRead)\n}\n\n// after\nif errRead != nil {\n\treturn nil, fmt.Errorf(\"antigravity token exchange: read response (status %d): %w\", resp.StatusCode, errRead)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"read response\") {\n\t// body read interrupted; status unknown — obtain a new auth code and retry once\n}","preventionTips":["Size timeouts to include body reads, not just headers","Treat partial error bodies as retryable only with a fresh code"],"tags":["oauth","antigravity","network","response-body"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}