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

  1. Retry the refresh with the same refresh token — the grant is idempotent from the client's perspective unless the provider rotates and invalidates it
  2. Inspect the wrapped error (unexpected EOF vs deadline exceeded) to choose between network fix and timeout increase
  3. Raise the client timeout or move refresh earlier, before expiry pressure
  4. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/fff0987a35e81395. Report an issue: GitHub.