sipeed/picoclaw · error
no refresh token available
Error message
no refresh token available
What it means
RefreshAccessToken (pkg/auth/oauth.go:440) refuses to run when cred.RefreshToken is an empty string. The credential was created without a refresh token — typically via LoginPasteToken (API-key auth sets only AccessToken) or an OAuth flow where the provider did not return refresh_token — so there is nothing to refresh with.
Source
Thrown at pkg/auth/oauth.go:440
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")
}
data := url.Values{
"client_id": {cfg.ClientID},
"grant_type": {"refresh_token"},
"refresh_token": {cred.RefreshToken},
"scope": {"openid profile email"},
}
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)View on GitHub (pinned to 49183d7e8d)
Solutions
- Check cred.AuthMethod and cred.RefreshToken before calling RefreshAccessToken; if empty, prompt re-login via OAuth (LoginDeviceCode / browser flow) to obtain a refresh token
- For providers that need it, request offline access (e.g. Google's access_type=offline&prompt=consent — already set in buildAuthorizeURL for accounts.google.com issuers)
- If using API-key auth, skip refresh entirely and keep the static AccessToken until it expires or is revoked
- Persist the full credential after login so RefreshToken survives restarts
Example fix
// before
refreshed, err := auth.RefreshAccessToken(cred, cfg)
// after
if cred.RefreshToken == "" {
return nil, fmt.Errorf("cannot refresh: credential has no refresh token (auth method %q); re-login with OAuth", cred.AuthMethod)
}
refreshed, err := auth.RefreshAccessToken(cred, cfg) Defensive patterns
Strategy: validation
Validate before calling
func canRefresh(cred *auth.AuthCredential) bool {
return cred != nil && strings.TrimSpace(cred.RefreshToken) != ""
}
// before refreshing:
if !canRefresh(cred) {
return fmt.Errorf("no refresh token on credential (auth method %q); re-login required", cred.AuthMethod)
} Type guard
func isNoRefreshTokenError(err error) bool {
return err != nil && strings.Contains(err.Error(), "no refresh token available")
} Try / catch
refreshed, err := auth.RefreshAccessToken(cred, cfg)
if err != nil {
if isNoRefreshTokenError(err) {
// not retriable: route the user to re-login
refreshed, err = reloginFlow(cfg)
}
if err != nil {
return err
}
} Prevention
- Check cred.RefreshToken before calling RefreshAccessToken
- Request offline access scopes at authorization time
- Persist complete credentials (including RefreshToken) immediately after login
- Distinguish API-key credentials (AuthMethod 'token') from OAuth ones in refresh logic
When it happens
Trigger: Calling RefreshAccessToken on a credential whose AuthMethod is "token" (pasted API key), or an OAuth credential where the token response omitted refresh_token (offline access not granted).
Common situations: Mixing auth modes: user logged in with a pasted key but code path assumes OAuth; Google-style issuers that require access_type=offline for refresh tokens; provider returned 200 without refresh_token on first exchange; tests constructing AuthCredential literals without RefreshToken.
Related errors
- refreshing token: %w
- reading token refresh response: %w
- token refresh failed: %s
- credential: file:// reference has no filename
- credential: enc:// payload too short
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/a8f06eca18dcd29a.
Report an issue: GitHub.