From e19d3ed2b28fdebf0f1f1b51f47d3ff3be827b06 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:00:36 -0700 Subject: [PATCH] Pin WSL-gate and same-token short-circuit for legacy keychain retry The legacy-keychain retry must never answer a WSL target with the host user's keychain account, and must not double the usage request when the legacy item mirrors the failed scoped token. Add tests locking both. --- src/main/rate-limits/claude-fetcher.test.ts | 65 +++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/main/rate-limits/claude-fetcher.test.ts b/src/main/rate-limits/claude-fetcher.test.ts index 7626b0d6cd5..96c1c1c75ae 100644 --- a/src/main/rate-limits/claude-fetcher.test.ts +++ b/src/main/rate-limits/claude-fetcher.test.ts @@ -276,6 +276,71 @@ describe('fetchClaudeRateLimits', () => { expect(readActiveClaudeKeychainCredentialsStrict).not.toHaveBeenCalledWith(undefined) }) + it('does not retry with the legacy keychain for WSL targets', async () => { + // Why: a WSL target's stale credentials must never be answered with the + // host user's legacy macOS Keychain account. + const configDir = '\\\\wsl$\\Ubuntu\\home\\test\\.claude' + const authPreparation: ClaudeRuntimeAuthPreparation = { + configDir, + runtime: 'wsl', + wslDistro: 'Ubuntu', + wslLinuxConfigDir: '/home/test/.claude', + envPatch: { CLAUDE_CONFIG_DIR: '/home/test/.claude' }, + stripAuthEnv: false, + provenance: 'system' + } + vi.mocked(readActiveClaudeKeychainCredentialsStrict).mockImplementation(async (dir) => + dir + ? JSON.stringify({ claudeAiOauth: { accessToken: 'stale-wsl-token' } }) + : JSON.stringify({ claudeAiOauth: { accessToken: 'fresh-legacy-token' } }) + ) + netFetchMock.mockResolvedValue( + new Response(JSON.stringify({ error: { message: 'Invalid authentication credentials' } }), { + status: 401 + }) + ) + + await expect( + fetchClaudeRateLimits({ authPreparation, allowPtyFallback: false }) + ).resolves.toMatchObject({ + provider: 'claude', + status: 'error' + }) + + expect(netFetchMock).toHaveBeenCalledTimes(1) + expect(readActiveClaudeKeychainCredentialsStrict).not.toHaveBeenCalledWith(undefined) + }) + + it('skips the legacy retry when the legacy item mirrors the failed scoped token', async () => { + // Why: Claude's usage endpoint has a tight request budget; retrying the + // identical token would double the request for a guaranteed second 401. + const configDir = '/Users/test/.claude' + const authPreparation: ClaudeRuntimeAuthPreparation = { + configDir, + envPatch: { CLAUDE_CONFIG_DIR: configDir }, + stripAuthEnv: false, + provenance: 'system' + } + vi.mocked(readActiveClaudeKeychainCredentialsStrict).mockResolvedValue( + JSON.stringify({ claudeAiOauth: { accessToken: 'mirrored-token' } }) + ) + netFetchMock.mockResolvedValue( + new Response(JSON.stringify({ error: { message: 'Invalid authentication credentials' } }), { + status: 401 + }) + ) + + await expect( + fetchClaudeRateLimits({ authPreparation, allowPtyFallback: false }) + ).resolves.toMatchObject({ + provider: 'claude', + status: 'error' + }) + + expect(netFetchMock).toHaveBeenCalledTimes(1) + expect(readActiveClaudeKeychainCredentialsStrict).toHaveBeenCalledWith(undefined) + }) + it('accepts Claude Code statusline-style rate limit window fields', async () => { const configDir = '/Users/test/.claude' const authPreparation: ClaudeRuntimeAuthPreparation = {