()
| 1375 | // then revokes terminal 1 server-side, and terminal 1's memoize never |
| 1376 | // re-reads — infinite /login regress (CC-1096, GH#24317). |
| 1377 | async function invalidateOAuthCacheIfDiskChanged(): Promise<void> { |
| 1378 | try { |
| 1379 | const { mtimeMs } = await stat( |
| 1380 | join(getClaudeConfigHomeDir(), '.credentials.json'), |
| 1381 | ) |
| 1382 | if (mtimeMs !== lastCredentialsMtimeMs) { |
| 1383 | lastCredentialsMtimeMs = mtimeMs |
| 1384 | clearOAuthTokenCache() |
| 1385 | } |
| 1386 | } catch { |
| 1387 | // ENOENT — macOS keychain path (file deleted on migration). Clear only |
| 1388 | // the memoize so it delegates to the keychain cache's 30s TTL instead |
| 1389 | // of caching forever on top. `security find-generic-password` is |
| 1390 | // ~15ms; bounded to once per 30s by the keychain cache. |
| 1391 | getClaudeAIOAuthTokens.cache?.clear?.() |
| 1392 | } |
| 1393 | } |
| 1394 | |
| 1395 | // In-flight dedup: when N claude.ai proxy connectors hit 401 with the same |
| 1396 | // token simultaneously (common at startup — #20930), only one should clear |
no test coverage detected