From 0af22a558fb87a67eebf6985ac8665df19569f2c Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Fri, 13 Feb 2026 18:09:28 -0300 Subject: [PATCH] fix(oauth): prevent connection test from corrupting valid tokens Only attempt token refresh on 401/403 during connection tests when the token is actually expired (isTokenExpired). Previously, any 401/403 triggered an aggressive refresh that could overwrite valid tokens when the upstream returned transient errors (rate-limiting, etc.). Fixes Cline, Qwen, and iFlow losing authentication after tests. --- src/app/api/providers/[id]/test/route.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/api/providers/[id]/test/route.js b/src/app/api/providers/[id]/test/route.js index da9576d0e7..8c5c76e142 100644 --- a/src/app/api/providers/[id]/test/route.js +++ b/src/app/api/providers/[id]/test/route.js @@ -376,11 +376,13 @@ async function testOAuthConnection(connection) { }; } - // If 401/403 and we haven't tried refresh yet, try refresh now - // (attempt refresh for ANY provider with a refreshToken, not just those marked refreshable) + // If 401/403 and we haven't tried refresh yet, only attempt refresh + // if the token is actually expired. This prevents corrupting valid tokens + // when the upstream returns transient 401/403 errors (rate-limiting, etc.). if ( (res.status === 401 || res.status === 403) && !refreshed && + isTokenExpired(connection) && connection.refreshToken && typeof connection.refreshToken === "string" ) { @@ -405,7 +407,7 @@ async function testOAuthConnection(connection) { }; } - const error = `API returned ${retryRes.status}`; + const error = `API returned ${retryRes.status} after token refresh`; return { valid: false, error, @@ -414,7 +416,7 @@ async function testOAuthConnection(connection) { diagnosis: classifyFailure({ error, statusCode: retryRes.status }), }; } - const error = "Token invalid or revoked"; + const error = "Token expired and refresh failed"; return { valid: false, error,