google-gemini/gemini-cli

OAuth refresh token lost during token refresh, causing 'No refresh token is set' after ~1 hour

Open

#21,691 opened on Mar 9, 2026

View on GitHub
 (9 comments) (0 reactions) (1 assignee)TypeScript (13,657 forks)batch import
area/corearea/securityeffort/smallhelp wantedkind/bugpriority/p1status/bot-triaged

Repository metrics

Stars
 (103,992 stars)
PR merge metrics
 (Avg merge 4d 11h) (62 merged PRs in 30d)

Description

Problem

When Google OAuth refreshes an access token, the tokens event payload only contains the new access_token — it does not resend the refresh_token. However, both OAuthCredentialStorage.saveCredentials() and the file-based cacheCredentials() overwrite the stored credentials entirely, wiping out the existing refresh token.

This causes sessions to fail with API Error: No refresh token is set after the first access token expiry (~1 hour).

Additionally

deleteCredentials() in both FileTokenStorage and KeychainTokenStorage throws an error when the credential doesn't exist (e.g., was already deleted). This causes a cascading "Failed to clear OAuth credentials" error loop when re-auth tries to clear credentials that are already gone, preventing users from re-authenticating.

Steps to reproduce

  1. Authenticate with Google OAuth
  2. Use the CLI for >1 hour (until access token expires)
  3. Send another message → No refresh token is set
  4. CLI attempts to clear credentials and re-auth → repeated "Failed to clear OAuth credentials" errors

Expected behavior

  • Refresh tokens should be preserved when saving a token refresh (merge with existing rather than overwrite)
  • Deleting non-existent credentials should be a no-op

Environment

  • macOS, using GEMINI_FORCE_ENCRYPTED_FILE_STORAGE=true
  • Affects both encrypted (OAuthCredentialStorage) and file-based (cacheCredentials) storage paths

Contributor guide