( retryCount: number, force: boolean, )
| 1495 | } |
| 1496 | |
| 1497 | async function checkAndRefreshOAuthTokenIfNeededImpl( |
| 1498 | retryCount: number, |
| 1499 | force: boolean, |
| 1500 | ): Promise<boolean> { |
| 1501 | const MAX_RETRIES = 5 |
| 1502 | |
| 1503 | await invalidateOAuthCacheIfDiskChanged() |
| 1504 | |
| 1505 | // First check if token is expired with cached value |
| 1506 | // Skip this check if force=true (server already told us token is bad) |
| 1507 | const tokens = getClaudeAIOAuthTokens() |
| 1508 | if (!force) { |
| 1509 | if (!tokens?.refreshToken || !isOAuthTokenExpired(tokens.expiresAt)) { |
| 1510 | return false |
| 1511 | } |
| 1512 | } |
| 1513 | |
| 1514 | if (!tokens?.refreshToken) { |
| 1515 | return false |
| 1516 | } |
| 1517 | |
| 1518 | if (!shouldUseClaudeAIAuth(tokens.scopes)) { |
| 1519 | return false |
| 1520 | } |
| 1521 | |
| 1522 | // Re-read tokens async to check if they're still expired |
| 1523 | // Another process might have refreshed them |
| 1524 | getClaudeAIOAuthTokens.cache?.clear?.() |
| 1525 | clearKeychainCache() |
| 1526 | const freshTokens = await getClaudeAIOAuthTokensAsync() |
| 1527 | if ( |
| 1528 | !freshTokens?.refreshToken || |
| 1529 | !isOAuthTokenExpired(freshTokens.expiresAt) |
| 1530 | ) { |
| 1531 | return false |
| 1532 | } |
| 1533 | |
| 1534 | // Tokens are still expired, try to acquire lock and refresh |
| 1535 | const claudeDir = getClaudeConfigHomeDir() |
| 1536 | await mkdir(claudeDir, { recursive: true }) |
| 1537 | |
| 1538 | let release |
| 1539 | try { |
| 1540 | logEvent('tengu_oauth_token_refresh_lock_acquiring', {}) |
| 1541 | release = await lockfile.lock(claudeDir) |
| 1542 | logEvent('tengu_oauth_token_refresh_lock_acquired', {}) |
| 1543 | } catch (err) { |
| 1544 | if ((err as { code?: string }).code === 'ELOCKED') { |
| 1545 | // Another process has the lock, let's retry if we haven't exceeded max retries |
| 1546 | if (retryCount < MAX_RETRIES) { |
| 1547 | logEvent('tengu_oauth_token_refresh_lock_retry', { |
| 1548 | retryCount: retryCount + 1, |
| 1549 | }) |
| 1550 | // Wait a bit before retrying |
| 1551 | await sleep(1000 + Math.random() * 1000) |
| 1552 | return checkAndRefreshOAuthTokenIfNeededImpl(retryCount + 1, force) |
| 1553 | } |
| 1554 | logEvent('tengu_oauth_token_refresh_lock_retry_limit_reached', { |
no test coverage detected