{"record":{"id":"fff0987a35e81395","repo":"sipeed/picoclaw","slug":"reading-token-refresh-response-w","errorCode":null,"errorMessage":"reading token refresh response: %w","messagePattern":"reading token refresh response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/auth/oauth.go","lineNumber":466,"sourceCode":"\t}\n\tif cfg.ClientSecret != \"\" {\n\t\tdata.Set(\"client_secret\", cfg.ClientSecret)\n\t}\n\n\ttokenURL := cfg.Issuer + \"/oauth/token\"\n\tif cfg.TokenURL != \"\" {\n\t\ttokenURL = cfg.TokenURL\n\t}\n\n\tresp, err := http.PostForm(tokenURL, data)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"refreshing token: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading token refresh response: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"token refresh failed: %s\", string(body))\n\t}\n\n\trefreshed, err := parseTokenResponse(body, cred.Provider)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif refreshed.RefreshToken == \"\" {\n\t\trefreshed.RefreshToken = cred.RefreshToken\n\t}\n\tif refreshed.AccountID == \"\" {\n\t\trefreshed.AccountID = cred.AccountID\n\t}\n\tif cred.Email != \"\" && refreshed.Email == \"\" {\n\t\trefreshed.Email = cred.Email\n\t}","sourceCodeStart":448,"sourceCodeEnd":484,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/auth/oauth.go#L448-L484","documentation":"RefreshAccessToken (pkg/auth/oauth.go:466) got an HTTP response from the token endpoint but io.ReadAll failed streaming the body, so the refresh result (which may already have been applied server-side) cannot be read. The old credential remains valid locally.","triggerScenarios":"Connection reset mid-body on POST {TokenURL or Issuer}/oauth/token; client context timeout during body read; proxy truncating the response.","commonSituations":"Short http.Client.Timeout vs slow token endpoints; flaky networks during automated refresh loops in long-running CLIs; rare compared to status-code failures.","solutions":["Retry the refresh with the same refresh token — the grant is idempotent from the client's perspective unless the provider rotates and invalidates it","Inspect the wrapped error (unexpected EOF vs deadline exceeded) to choose between network fix and timeout increase","Raise the client timeout or move refresh earlier, before expiry pressure","Check proxies for response truncation"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"func isBodyReadError(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"reading token refresh response\")\n}","tryCatchPattern":"refreshed, err := auth.RefreshAccessToken(cred, cfg)\nif err != nil && isBodyReadError(err) {\n\ttime.Sleep(time.Second)\n\trefreshed, err = auth.RefreshAccessToken(cred, cfg)\n\tif err != nil && isBodyReadError(err) {\n\t\t// possible refresh-token rotation loss: fall back to re-login\n\t\treturn reloginFlow(cfg)\n\t}\n}","preventionTips":["Retry mid-body failures once, then re-login — providers with rotating refresh tokens may have consumed the old one","Refresh early and often enough that a single failed attempt is recoverable","Persist new credentials atomically after each successful refresh"],"tags":["network","io","transient","refresh-token","oauth","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}