diff --git a/src/main/rate-limits/codex-fetcher-auth-errors.test.ts b/src/main/rate-limits/codex-fetcher-auth-errors.test.ts index 43b3bc44e5d..b172551e1dd 100644 --- a/src/main/rate-limits/codex-fetcher-auth-errors.test.ts +++ b/src/main/rate-limits/codex-fetcher-auth-errors.test.ts @@ -97,6 +97,51 @@ describe('fetchCodexRateLimits auth errors', () => { expect(ptySpawnMock).not.toHaveBeenCalled() }) + it('returns the app-server chatgpt-auth-required error without spawning the PTY probe', async () => { + const rpcChild = makeRpcChild() + const authError = 'chatgpt authentication required to read rate limits' + + childSpawnMock.mockReturnValue(rpcChild) + rpcChild.stdin.write.mockImplementation((line: string) => { + const msg = JSON.parse(line) as { id?: number; method?: string } + if (msg.method === 'initialize') { + setTimeout(() => { + rpcChild.stdout.emit( + 'data', + Buffer.from(`${JSON.stringify({ jsonrpc: '2.0', id: msg.id, result: {} })}\n`) + ) + }, 0) + } + if (msg.method === 'account/rateLimits/read') { + setTimeout(() => { + rpcChild.stdout.emit( + 'data', + Buffer.from( + `${JSON.stringify({ + jsonrpc: '2.0', + id: msg.id, + error: { code: -32600, message: authError } + })}\n` + ) + ) + }, 0) + } + }) + + const resultPromise = fetchCodexRateLimits() + await vi.advanceTimersByTimeAsync(1) + await vi.advanceTimersByTimeAsync(1) + + await expect(resultPromise).resolves.toMatchObject({ + provider: 'codex', + session: null, + weekly: null, + status: 'error', + error: authError + }) + expect(ptySpawnMock).not.toHaveBeenCalled() + }) + it('preserves Codex PTY auth errors when the CLI exits before status is available', async () => { const ptyHandlers: { onData?: (data: string) => void; onExit?: () => void } = {} const authError = diff --git a/src/shared/codex-auth-errors.ts b/src/shared/codex-auth-errors.ts index 04bf9c9f71e..9f25a4b444c 100644 --- a/src/shared/codex-auth-errors.ts +++ b/src/shared/codex-auth-errors.ts @@ -7,7 +7,11 @@ const CODEX_AUTH_ERROR_PATTERNS = [ /please reauthenticate/i, /not logged in/i, /token data is not available/i, - /auth (?:is missing|tokens are missing|does not expose)/i + /auth (?:is missing|tokens are missing|does not expose)/i, + // Why: app-server rejects account/rateLimits/read with this when auth.json + // holds only an API key; without classification the fetcher falls through to + // a hidden PTY probe that can only time out (15s) on every refresh. + /chatgpt authentication required/i ] const ANSI_ESCAPE_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*[a-zA-Z]`, 'g')