From eb9acdd2bf8936490ec882e2519c78aa1c655468 Mon Sep 17 00:00:00 2001 From: Raphael Manke Date: Wed, 18 Mar 2026 21:33:58 +0100 Subject: [PATCH] fix: handle invalid_grant as permanent auth error with improved resilience - Add 'Invalid grant provided' and 'invalid_grant' to isPermanentError() so accounts fail fast instead of retrying 10 times with a dead token - Catch 'Invalid grant provided' message in token-refresher error handler - Fix CLI sync skip condition to also check token is not already expired (prevents stale-but-healthy tokens from blocking fresher CLI tokens) - Log writeToKiroCli failures instead of silently swallowing them Fixes cases where refresh token rotation or server-side revocation caused KiroTokenRefreshError: Refresh failed: Invalid grant provided --- src/core/auth/token-refresher.ts | 3 ++- src/plugin/accounts.ts | 2 +- src/plugin/health.ts | 2 ++ src/plugin/sync/kiro-cli.ts | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/core/auth/token-refresher.ts b/src/core/auth/token-refresher.ts index b6c96da..73a64c9 100644 --- a/src/core/auth/token-refresher.ts +++ b/src/core/auth/token-refresher.ts @@ -70,7 +70,8 @@ export class TokenRefresher { error.code === 'InvalidTokenException' || error.code === 'HTTP_401' || error.code === 'HTTP_403' || - error.message.includes('Invalid refresh token provided')) + error.message.includes('Invalid refresh token provided') || + error.message.includes('Invalid grant provided')) ) { this.accountManager.markUnhealthy(account, error.message) await this.repository.batchSave(this.accountManager.getAccounts()) diff --git a/src/plugin/accounts.ts b/src/plugin/accounts.ts index c07a5b7..f72a9a4 100644 --- a/src/plugin/accounts.ts +++ b/src/plugin/accounts.ts @@ -177,7 +177,7 @@ export class AccountManager { delete acc.unhealthyReason delete acc.recoveryTime kiroDb.upsertAccount(acc).catch(() => {}) - writeToKiroCli(acc).catch(() => {}) + writeToKiroCli(acc).catch((e) => logger.warn('Failed to write token back to Kiro CLI', e)) } } markRateLimited(a: ManagedAccount, ms: number): void { diff --git a/src/plugin/health.ts b/src/plugin/health.ts index 3e061a9..1f1db1f 100644 --- a/src/plugin/health.ts +++ b/src/plugin/health.ts @@ -2,6 +2,8 @@ export function isPermanentError(reason?: string): boolean { if (!reason) return false return ( reason.includes('Invalid refresh token') || + reason.includes('Invalid grant provided') || + reason.includes('invalid_grant') || reason.includes('ExpiredTokenException') || reason.includes('InvalidTokenException') || reason.includes('HTTP_401') || diff --git a/src/plugin/sync/kiro-cli.ts b/src/plugin/sync/kiro-cli.ts index f0d21c6..6e46b01 100644 --- a/src/plugin/sync/kiro-cli.ts +++ b/src/plugin/sync/kiro-cli.ts @@ -130,7 +130,8 @@ export async function syncFromKiroCli() { if ( existingById && existingById.is_healthy === 1 && - existingById.expires_at >= cliExpiresAt + existingById.expires_at >= cliExpiresAt && + existingById.expires_at > Date.now() ) continue