diff --git a/src/main/rate-limits/service.test.ts b/src/main/rate-limits/service.test.ts index dfe2755bdc2..245ebed42d2 100644 --- a/src/main/rate-limits/service.test.ts +++ b/src/main/rate-limits/service.test.ts @@ -1751,6 +1751,60 @@ describe('RateLimitService', () => { ) }) + it('caches an outgoing weekly-only Codex account so the switcher keeps its inline bars', async () => { + const service = new RateLimitService() + service.setInactiveCodexAccountsResolver(() => [ + { id: 'account-weekly', managedHomePath: '/tmp/account-weekly/home' } + ]) + + const weeklyOnly: ProviderRateLimits = { + provider: 'codex', + session: null, + weekly: { usedPercent: 76, windowMinutes: 10080, resetsAt: null, resetDescription: null }, + updatedAt: Date.now(), + error: null, + status: 'ok' + } + vi.mocked(fetchClaudeRateLimits).mockResolvedValueOnce(okProvider('claude', 10, Date.now())) + vi.mocked(fetchCodexRateLimits) + .mockResolvedValueOnce(weeklyOnly) + .mockResolvedValueOnce(okProvider('codex', 40, Date.now())) + + await service.refresh() + await service.refreshForCodexAccountChange('account-weekly') + + expect(service.getState().inactiveCodexAccounts).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + accountId: 'account-weekly', + rateLimits: expect.objectContaining({ + session: null, + weekly: expect.objectContaining({ usedPercent: 76 }) + }) + }) + ]) + ) + }) + + it('does not cache an outgoing Codex account that has no usage windows', async () => { + const service = new RateLimitService() + service.setInactiveCodexAccountsResolver(() => [ + { id: 'account-empty', managedHomePath: '/tmp/account-empty/home' } + ]) + + vi.mocked(fetchClaudeRateLimits).mockResolvedValueOnce(okProvider('claude', 10, Date.now())) + vi.mocked(fetchCodexRateLimits) + .mockResolvedValueOnce(errorProvider('codex', 'codex not signed in')) + .mockResolvedValueOnce(okProvider('codex', 40, Date.now())) + + await service.refresh() + await service.refreshForCodexAccountChange('account-empty') + + expect(service.getState().inactiveCodexAccounts).not.toEqual( + expect.arrayContaining([expect.objectContaining({ accountId: 'account-empty' })]) + ) + }) + it('does not cache host Claude usage under an outgoing WSL account', async () => { const service = new RateLimitService() service.setInactiveClaudeAccountsResolver(() => [ diff --git a/src/main/rate-limits/service.ts b/src/main/rate-limits/service.ts index a1700729ac8..4b0a5e266af 100644 --- a/src/main/rate-limits/service.ts +++ b/src/main/rate-limits/service.ts @@ -378,9 +378,11 @@ export class RateLimitService { target?: CodexAccountSelectionTarget ): Promise { const nextTarget = normalizeCodexAccountSelectionTarget(target) + // Why: weekly-only plans report no session window, so gating on session alone + // dropped their snapshot and left the switcher's inline bars empty. if ( outgoingAccountId && - this.state.codex?.session && + (this.state.codex?.session || this.state.codex?.weekly) && this.isSameCodexTarget(this.codexFetchTarget, nextTarget) ) { this.inactiveCodexCache.set(outgoingAccountId, this.state.codex)