gemini-cli - ✅(Solved) Fix OAuth refresh tokens discarded on access_token rotation and on stale-credential delete [2 pull requests, 2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
google-gemini/gemini-cli#25460Fetched 2026-04-16 07:06:08
View on GitHub
Comments
2
Participants
2
Timeline
9
Reactions
0
Author
Timeline (top)
labeled ×3commented ×2cross-referenced ×2mentioned ×1

Fix Action

Fixed

PR fix notes

PR #21695: fix(core): preserve refresh tokens on save, tolerate missing on delete

Description (problem / solution / changelog)

Fixes #21691

Summary

  • saveCredentials: merge with existing stored refresh token when Google only sends a new access_token during token refresh (both encrypted and file-based paths)
  • isExpired: don't discard credentials that have a refresh token — the caller can silently refresh the access token
  • Add prompt: 'consent' to OAuth flows to ensure Google always returns a refresh token

Test plan

  • Manual: authenticate, wait >1 hour, verify refresh works without re-auth
  • Verified refresh token preserved across token refresh events

Changed files

  • packages/core/src/code_assist/oauth-credential-storage.ts (modified, +16/-2)
  • packages/core/src/code_assist/oauth2.ts (modified, +19/-1)
  • packages/core/src/mcp/token-storage/base-token-storage.ts (modified, +7/-0)

PR #25464: fix(core): preserve refresh tokens on access_token rotation

Description (problem / solution / changelog)

Fixes #25460

Summary

When Google OAuth refreshes an access token, the tokens event typically only includes the new access_tokenrefresh_token is NOT resent. OAuthCredentialStorage.saveCredentials() and cacheCredentials() both overwrote stored credentials verbatim, wiping the refresh token. After ~1 hour users hit "No refresh token is set" and got kicked back to full OAuth re-auth.

Changes

  • saveCredentials: merge with the existing stored refresh_token when the incoming credentials don't include one (both encrypted and file-based paths)
  • isExpired: keep credentials that have a refresh_token — the caller can silently refresh the access token rather than discard
  • oauth2.ts: include prompt: 'consent' so Google reliably returns a refresh token (not only on first authorization)

Test plan

  • Unit tests for merge behavior on access_token-only refresh
  • Manual: authenticate, wait past token expiry, confirm silent refresh (no re-auth prompt)
  • Manual: confirm first-time auth still works

Replaces #21695 (auto-closed per contribution policy).

Changed files

  • packages/core/src/code_assist/oauth-credential-storage.ts (modified, +16/-2)
  • packages/core/src/code_assist/oauth2.ts (modified, +19/-1)
  • packages/core/src/mcp/token-storage/base-token-storage.ts (modified, +7/-0)
RAW_BUFFERClick to expand / collapse

Bug

Two issues in OAuth credential handling:

  1. saveCredentials overwrites refresh tokens: Google's token refresh often returns only a new access_token (no refresh_token). The current code writes the new response verbatim, which drops the stored refresh_token to undefined. Next refresh then fails, forcing a full re-auth.
  2. isExpired discards credentials that could silently refresh: credentials with a valid refresh_token are treated as unusable on expiry instead of letting the caller refresh the access token.

Impact

Users get kicked out to full OAuth re-auth periodically despite having a valid refresh token stored.

Proposal

  • saveCredentials (both encrypted and file-based paths): merge with existing stored refresh_token when the new payload lacks one
  • isExpired: keep credentials that have a refresh_token and let the caller refresh
  • Tolerate missing credentials on delete (idempotent)

Related

Fix proposed in #21695 (closed per contribution policy).

extent analysis

TL;DR

Update the saveCredentials function to merge new tokens with existing stored refresh tokens and modify the isExpired check to allow silent refreshes for credentials with valid refresh tokens.

Guidance

  • Review the current implementation of saveCredentials to ensure it handles cases where the new token response lacks a refresh_token, and update it to preserve the existing stored refresh_token in such cases.
  • Modify the isExpired function to check for the presence of a valid refresh_token and allow the caller to refresh the access token instead of discarding the credentials.
  • Consider implementing idempotent behavior for credential deletion to tolerate missing credentials.
  • Verify the changes by testing scenarios where the refresh token is missing from the new token response and where credentials are near expiry.

Example

// Pseudocode example for updated saveCredentials function
function saveCredentials(newToken) {
  const existingCredentials = getStoredCredentials();
  if (!newToken.refresh_token && existingCredentials.refresh_token) {
    newToken.refresh_token = existingCredentials.refresh_token;
  }
  // Proceed with saving the updated credentials
}

Notes

The proposed changes aim to address the specific issues mentioned, but additional testing and review may be necessary to ensure the updates do not introduce new problems or affect other parts of the system.

Recommendation

Apply the proposed workaround by updating the saveCredentials and isExpired functions as described, to prevent unnecessary re-authentication and ensure seamless token refresh.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING