()
| 1318 | // then revokes terminal 1 server-side, and terminal 1's memoize never |
| 1319 | // re-reads — infinite /login regress (CC-1096, GH#24317). |
| 1320 | async function invalidateOAuthCacheIfDiskChanged(): Promise<void> { |
| 1321 | try { |
| 1322 | const { mtimeMs } = await stat( |
| 1323 | join(getClaudeConfigHomeDir(), '.credentials.json'), |
| 1324 | ) |
| 1325 | if (mtimeMs !== lastCredentialsMtimeMs) { |
| 1326 | lastCredentialsMtimeMs = mtimeMs |
| 1327 | clearOAuthTokenCache() |
| 1328 | } |
| 1329 | } catch { |
| 1330 | // ENOENT — macOS keychain path (file deleted on migration). Clear only |
| 1331 | // the memoize so it delegates to the keychain cache's 30s TTL instead |
| 1332 | // of caching forever on top. `security find-generic-password` is |
| 1333 | // ~15ms; bounded to once per 30s by the keychain cache. |
| 1334 | getClaudeAIOAuthTokens.cache?.clear?.() |
| 1335 | } |
| 1336 | } |
| 1337 | |
| 1338 | // In-flight dedup: when N claude.ai proxy connectors hit 401 with the same |
| 1339 | // token simultaneously (common at startup — #20930), only one should clear |
no test coverage detected