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