google-gemini / google-gemini/gemini-cli
OAuth refresh token lost during token refresh, causing 'No refresh token is set' after ~1 hour
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
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
Assessment
This issue has not been assessed yet.