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