sipeed/picoclaw · error

reading device token response: %w

Error message

reading device token response: %w

What it means

pollDeviceCode (pkg/auth/oauth.go:422) received HTTP 200 from {Issuer}/api/accounts/deviceauth/token (meaning the user has approved) but io.ReadAll failed while streaming the token payload. The authorization code inside that body is lost, so the poll must be retried.

Source

Thrown at pkg/auth/oauth.go:422

	})

	resp, err := http.Post(
		cfg.Issuer+"/api/accounts/deviceauth/token",
		"application/json",
		strings.NewReader(string(reqBody)),
	)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("pending")
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("reading device token response: %w", err)
	}

	var tokenResp struct {
		AuthorizationCode string `json:"authorization_code"`
		CodeChallenge     string `json:"code_challenge"`
		CodeVerifier      string `json:"code_verifier"`
	}
	if err := json.Unmarshal(body, &tokenResp); err != nil {
		return nil, err
	}

	redirectURI := cfg.Issuer + "/deviceauth/callback"
	return ExchangeCodeForTokens(cfg, tokenResp.AuthorizationCode, tokenResp.CodeVerifier, redirectURI)
}

func RefreshAccessToken(cred *AuthCredential, cfg OAuthProviderConfig) (*AuthCredential, error) {
	if cred.RefreshToken == "" {
		return nil, fmt.Errorf("no refresh token available")

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Retry the poll: for device flows the server typically keeps the authorization available, the next poll re-POSTs and re-reads
  2. Inspect the wrapped error for 'unexpected EOF' vs 'context deadline exceeded' to pick truncation vs timeout
  3. Increase http.Client timeout if a custom short one is in use
  4. Check intermediary proxies for response-size or idle limits
Defensive patterns

Strategy: retry

Type guard

func isTokenBodyReadError(err error) bool {
	return err != nil && strings.Contains(err.Error(), "reading device token response")
}

Try / catch

cred, err := auth.PollDeviceCodeOnce(cfg, deviceAuthID, userCode)
if err != nil && isTokenBodyReadError(err) {
	time.Sleep(interval)
	cred, err = auth.PollDeviceCodeOnce(cfg, deviceAuthID, userCode)
}

Prevention

When it happens

Trigger: Connection dropped or context canceled after status 200 but before body completion on the deviceauth/token POST; proxy truncation; very short client timeout on a slow response.

Common situations: Flaky VPN/wifi mid-flow; corporate proxy idle-killing the stream; rare relative to pending/parse paths. Note LoginDeviceCode swallows this error and retries on the next tick; only callers of PollDeviceCodeOnce see it directly.

Related errors


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