fix(oauth): preserve non-rotating providers' refresh_token on unrecoverable refresh (#3679) (#3766)

The proactive health-check refresh deactivation branch nulled the stored
refresh_token on any unrecoverable error (e.g. invalid_grant). That was
only meant for rotating one-time-use tokens (Codex/OpenAI); for
non-rotating Google-family providers (gemini-cli/antigravity/gemini) it
destroyed the user's only recovery artifact, leaving the connection
permanently showing 'No valid refresh token available'. Gate the null on
isRotatingProvider so non-rotating tokens are preserved.

Closes #3679
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-13 08:32:51 -03:00
committed by GitHub
parent d8b6ae3688
commit d1088f0dc6
3 changed files with 69 additions and 2 deletions

View File

@@ -543,13 +543,21 @@ export async function checkConnection(conn) {
await updateProviderConnection(conn.id, {
lastHealthCheckAt: now,
testStatus: "expired",
lastError: `Refresh token consumed (${result.error}). Please re-authenticate this account.`,
lastError: isRotatingProvider
? `Refresh token consumed (${result.error}). Please re-authenticate this account.`
: `Refresh token rejected (${result.error}). Please re-authenticate this account.`,
lastErrorAt: now,
lastErrorType: result.error,
lastErrorSource: "oauth",
errorCode: result.error,
isActive: false,
refreshToken: null,
// Only rotating-token providers (Codex/OpenAI/etc.) have single-use refresh
// tokens that are genuinely consumed and worthless after a failed refresh, so
// clearing them is safe. For non-rotating providers (Google: gemini-cli /
// antigravity / gemini) the stored refresh_token is the user's only recovery
// artifact — nulling it caused #3679 (the connection reports "No valid refresh
// token available" and can never recover even after re-activation). Preserve it.
...(isRotatingProvider ? { refreshToken: null } : {}),
});
logError(
`${LOG_PREFIX}${conn.provider}/${getConnectionLogLabel(conn)}` +