router-for-me/CLIProxyAPI · error
token refresh failed after %d attempts: %w
Error message
token refresh failed after %d attempts: %w
What it means
RefreshTokensWithRetry exhausted maxRetries attempts; the message wraps the last underlying error (network, status, or parse failure). The loop bails out early only for non-retryable errors containing refresh_token_reused, so reaching this message means every attempt failed with a retryable-looking error — usually sustained network failure or repeated non-200s like invalid_grant that do not match the reuse signature.
Source
Thrown at internal/auth/codex/openai_auth.go:328
return nil, ctx.Err()
case <-time.After(time.Duration(attempt) * time.Second):
}
}
tokenData, err := o.RefreshTokens(ctx, refreshToken)
if err == nil {
return tokenData, nil
}
if isNonRetryableRefreshErr(err) {
log.Warnf("Token refresh attempt %d failed with non-retryable error: %v", attempt+1, err)
return nil, err
}
lastErr = err
log.Warnf("Token refresh attempt %d failed: %v", attempt+1, err)
}
return nil, fmt.Errorf("token refresh failed after %d attempts: %w", maxRetries, lastErr)
}
func isNonRetryableRefreshErr(err error) bool {
if err == nil {
return false
}
raw := strings.ToLower(err.Error())
return strings.Contains(raw, "refresh_token_reused")
}
// UpdateTokenStorage updates an existing CodexTokenStorage with new token data.
// This is typically called after a successful token refresh to persist the new credentials.
func (o *CodexAuth) UpdateTokenStorage(storage *CodexTokenStorage, tokenData *CodexTokenData) {
storage.IDToken = tokenData.IDToken
storage.AccessToken = tokenData.AccessToken
storage.RefreshToken = tokenData.RefreshToken
storage.AccountID = tokenData.AccountID
storage.LastRefresh = time.Now().Format(time.RFC3339)View on GitHub (pinned to 78f0c4079e)
Solutions
- Unwrap the last error (errors.Unwrap / read the message tail) — it tells you whether this is network, status, or parse.
- Network cause: restore connectivity and trigger a refresh; consider a higher maxRetries with the built-in backoff.
- invalid_grant/revocation cause: re-authenticate (delete the codex auth file, run login) — retries cannot fix a dead token.
- Provider 5xx: check status page, retry later.
- If embedding the SDK, surface this error so credential health monitoring can mark the auth file as needing re-login.
Example fix
// before
td, err := o.RefreshTokensWithRetry(ctx, rt, 3)
if err != nil { log.Errorf("refresh failed") }
// after
td, err := o.RefreshTokensWithRetry(ctx, rt, 3)
if err != nil {
if strings.Contains(err.Error(), "invalid_grant") {
log.Errorf("refresh token no longer valid; re-login required")
} else {
log.Errorf("refresh failed after retries: %v", err)
}
} Defensive patterns
Strategy: retry
Try / catch
td, err := o.RefreshTokensWithRetry(ctx, rt, 3)
if err != nil {
if strings.Contains(err.Error(), "token refresh failed after") {
cause := errors.Unwrap(err)
if cause != nil && strings.Contains(cause.Error(), "invalid_grant") {
// credential is dead: re-login, more retries will not help
}
// otherwise network/upstream: schedule next periodic refresh
}
} Prevention
- Classify the wrapped lastErr before deciding retry vs re-login.
- Use shared storage backends when running multiple instances.
- Keep maxRetries modest (3) and let periodic scheduling provide long-run retries.
- Monitor refresh outcomes so permanent failures are visible early.
When it happens
Trigger: Sustained network outage across all attempts (each failing at dial); refresh token expired/revoked returning invalid_grant repeatedly (does not match the refresh_token_reused string, so it retries and fails every time); provider 5xx lasting longer than the retry window; context deadline hit on each attempt.
Common situations: Server offline when the refresh scheduler fires; revoked credential retried in a loop; upstream auth outage; maxRetries set too low for the transient condition.
Related errors
- token refresh request failed: %w
- failed to read refresh response: %w
- token refresh failed with status %d: %s
- decode Claude OAuth %s response: %w
- token exchange request failed: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/4b783503307405f4.
Report an issue: GitHub.