{"record":{"id":"5088bf19ab6eb379","repo":"router-for-me/CLIProxyAPI","slug":"failed-to-read-token-response-w-5088bf","errorCode":null,"errorMessage":"failed to read token response: %w","messagePattern":"failed to read token response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/codex/openai_auth.go","lineNumber":134,"sourceCode":"\treq, err := http.NewRequestWithContext(ctx, \"POST\", TokenURL, strings.NewReader(data.Encode()))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create token request: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tresp, err := o.httpClient.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"token exchange request failed: %w\", err)\n\t}\n\tdefer func() {\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read token response: %w\", err)\n\t}\n\t// log.Debugf(\"Token response: %s\", string(body))\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"token exchange failed with status %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\t// Parse token response\n\tvar tokenResp struct {\n\t\tAccessToken  string `json:\"access_token\"`\n\t\tRefreshToken string `json:\"refresh_token\"`\n\t\tIDToken      string `json:\"id_token\"`\n\t\tTokenType    string `json:\"token_type\"`\n\t\tExpiresIn    int    `json:\"expires_in\"`\n\t}\n\n\tif err = json.Unmarshal(body, &tokenResp); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse token response: %w\", err)","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/codex/openai_auth.go#L116-L152","documentation":"io.ReadAll failed while draining the token exchange response body. The server was reached and responded, but the body read aborted — typically because the connection was reset mid-body, the context was canceled during the read, or a proxy closed the stream early. Distinct from a non-200 status: the status line was received but the payload is incomplete.","triggerScenarios":"Upstream or intermediary closes the connection after headers; context canceled while body is streaming; proxy response buffering limits truncating the body; very rare kernel-level socket errors.","commonSituations":"Flaky mobile/tethered networks during login; aggressive corporate proxies cutting long responses; racing shutdown signals canceling the request context mid-read.","solutions":["Retry the login — mid-body resets are overwhelmingly transient.","If reproducible, capture the wrapped error (unexpected EOF vs context.Canceled) to see who cut the stream.","Bypass or fix the intermediary (proxy/VPN) suspected of truncating responses.","Ensure the context deadline covers the full body read, not just the dial."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"tok, err := auth.ExchangeCode(ctx, code)\nif err != nil && strings.Contains(err.Error(), \"failed to read token response\") {\n    // body truncation: transient; restart the flow rather than replaying the code\n    log.Warnf(\"truncated token response: %v\", err)\n}","preventionTips":["Prefer stable networks for interactive login.","Avoid proxies known to buffer/limit response bodies for auth domains.","Treat any single truncation as transient; two in a row means an intermediary problem."],"tags":["network","oauth","codex","io","transient","auth-flow"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}