( retryCount: number, force: boolean, )
| 1445 | } |
| 1446 | |
| 1447 | async function checkAndRefreshOAuthTokenIfNeededImpl( |
| 1448 | retryCount: number, |
| 1449 | force: boolean, |
| 1450 | ): Promise<boolean> { |
| 1451 | const MAX_RETRIES = 5 |
| 1452 | |
| 1453 | await invalidateOAuthCacheIfDiskChanged() |
| 1454 | |
| 1455 | // First check if token is expired with cached value |
| 1456 | // Skip this check if force=true (server already told us token is bad) |
| 1457 | const tokens = getClaudeAIOAuthTokens() |
| 1458 | if (!force) { |
| 1459 | if (!tokens?.refreshToken || !isOAuthTokenExpired(tokens.expiresAt)) { |
| 1460 | return false |
| 1461 | } |
| 1462 | } |
| 1463 | |
| 1464 | if (!tokens?.refreshToken) { |
| 1465 | return false |
| 1466 | } |
| 1467 | |
| 1468 | if (!shouldUseClaudeAIAuth(tokens.scopes)) { |
| 1469 | return false |
| 1470 | } |
| 1471 | |
| 1472 | // Re-read tokens async to check if they're still expired |
| 1473 | // Another process might have refreshed them |
| 1474 | getClaudeAIOAuthTokens.cache?.clear?.() |
| 1475 | clearKeychainCache() |
| 1476 | const freshTokens = await getClaudeAIOAuthTokensAsync() |
| 1477 | if ( |
| 1478 | !freshTokens?.refreshToken || |
| 1479 | !isOAuthTokenExpired(freshTokens.expiresAt) |
| 1480 | ) { |
| 1481 | return false |
| 1482 | } |
| 1483 | |
| 1484 | // Tokens are still expired, try to acquire lock and refresh |
| 1485 | const claudeDir = getClaudeConfigHomeDir() |
| 1486 | await mkdir(claudeDir, { recursive: true }) |
| 1487 | |
| 1488 | let release |
| 1489 | try { |
| 1490 | logEvent('tengu_oauth_token_refresh_lock_acquiring', {}) |
| 1491 | release = await lockfile.lock(claudeDir) |
| 1492 | logEvent('tengu_oauth_token_refresh_lock_acquired', {}) |
| 1493 | } catch (err) { |
| 1494 | if ((err as { code?: string }).code === 'ELOCKED') { |
| 1495 | // Another process has the lock, let's retry if we haven't exceeded max retries |
| 1496 | if (retryCount < MAX_RETRIES) { |
| 1497 | logEvent('tengu_oauth_token_refresh_lock_retry', { |
| 1498 | retryCount: retryCount + 1, |
| 1499 | }) |
| 1500 | // Wait a bit before retrying |
| 1501 | await sleep(1000 + Math.random() * 1000) |
| 1502 | return checkAndRefreshOAuthTokenIfNeededImpl(retryCount + 1, force) |
| 1503 | } |
| 1504 | logEvent('tengu_oauth_token_refresh_lock_retry_limit_reached', { |
no test coverage detected