{"record":{"id":"fb2481e4e73d2be2","repo":"router-for-me/CLIProxyAPI","slug":"failed-to-read-refresh-response-w-fb2481","errorCode":null,"errorMessage":"failed to read refresh response: %w","messagePattern":"failed to read refresh response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/codex/openai_auth.go","lineNumber":241,"sourceCode":"\t\treturn nil, fmt.Errorf(\"failed to create refresh request: %w\", errReq)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tresp, errDo := o.httpClient.Do(req)\n\tif errDo != nil {\n\t\treturn nil, fmt.Errorf(\"token refresh request failed: %w\", errDo)\n\t}\n\tdefer func() {\n\t\tif errClose := resp.Body.Close(); errClose != nil {\n\t\t\tlog.Errorf(\"token refresh response body close error: %v\", errClose)\n\t\t}\n\t}()\n\n\tbody, errRead := io.ReadAll(resp.Body)\n\tif errRead != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read refresh response: %w\", errRead)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"token refresh failed with status %d: %s\", resp.StatusCode, string(body))\n\t}\n\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 errUnmarshal := json.Unmarshal(body, &tokenResp); errUnmarshal != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse refresh response: %w\", errUnmarshal)\n\t}\n","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/codex/openai_auth.go#L223-L259","documentation":"io.ReadAll failed while reading the token refresh response body — the status line arrived but the body stream aborted (reset, truncation, or context cancellation during read). Functionally identical to error 203 but on the refresh path; it is a transport-level truncation, not a provider rejection.","triggerScenarios":"Connection reset after response headers during background refresh; context canceled mid-body; intermediary truncating the streamed response; keep-alive races with proxies.","commonSituations":"Background refresh on flaky networks; proxy idle-timeout closing the stream; shutdown races canceling the refresh context mid-read.","solutions":["Let the retry loop handle it — single truncations clear on retry.","If recurring, capture the wrapped error to identify who truncates (EOF vs reset vs canceled).","Exclude the auth host from problematic proxies/VPNs.","Ensure the refresh context deadline comfortably exceeds the full round trip."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"td, err := auth.RefreshTokensWithRetry(ctx, rt, 3)\nif err != nil && strings.Contains(err.Error(), \"failed to read refresh response\") {\n    // truncation: transient; next scheduled refresh will retry\n}","preventionTips":["Rely on the built-in backoff retry loop for background refreshes.","Investigate only after repeated truncations — then look at proxies."],"tags":["network","oauth","codex","io","token-refresh","transient"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}