sipeed/picoclaw · error
reading token refresh response: %w
Error message
reading token refresh response: %w
What it means
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.
Source
Thrown at pkg/auth/oauth.go:466
}
if cfg.ClientSecret != "" {
data.Set("client_secret", cfg.ClientSecret)
}
tokenURL := cfg.Issuer + "/oauth/token"
if cfg.TokenURL != "" {
tokenURL = cfg.TokenURL
}
resp, err := http.PostForm(tokenURL, data)
if err != nil {
return nil, fmt.Errorf("refreshing token: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading token refresh response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("token refresh failed: %s", string(body))
}
refreshed, err := parseTokenResponse(body, cred.Provider)
if err != nil {
return nil, err
}
if refreshed.RefreshToken == "" {
refreshed.RefreshToken = cred.RefreshToken
}
if refreshed.AccountID == "" {
refreshed.AccountID = cred.AccountID
}
if cred.Email != "" && refreshed.Email == "" {
refreshed.Email = cred.Email
}View on GitHub (pinned to 49183d7e8d)
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
Defensive patterns
Strategy: retry
Type guard
func isBodyReadError(err error) bool {
return err != nil && strings.Contains(err.Error(), "reading token refresh response")
} Try / catch
refreshed, err := auth.RefreshAccessToken(cred, cfg)
if err != nil && isBodyReadError(err) {
time.Sleep(time.Second)
refreshed, err = auth.RefreshAccessToken(cred, cfg)
if err != nil && isBodyReadError(err) {
// possible refresh-token rotation loss: fall back to re-login
return reloginFlow(cfg)
}
} Prevention
- 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
When it happens
Trigger: Connection reset mid-body on POST {TokenURL or Issuer}/oauth/token; client context timeout during body read; proxy truncating the response.
Common situations: Short http.Client.Timeout vs slow token endpoints; flaky networks during automated refresh loops in long-running CLIs; rare compared to status-code failures.
Related errors
- reading device token response: %w
- reading token exchange response: %w
- refreshing token: %w
- error fetching models: %w
- read error response: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/fff0987a35e81395.
Report an issue: GitHub.