diff --git a/src/main/rate-limits/gemini-oauth-sources.ts b/src/main/rate-limits/gemini-oauth-sources.ts index 386c8b281f3..b4cf80da569 100644 --- a/src/main/rate-limits/gemini-oauth-sources.ts +++ b/src/main/rate-limits/gemini-oauth-sources.ts @@ -68,7 +68,9 @@ export async function readGeminiCredentials(): Promise ) { return parsed as GeminiCredentials } - return null + // A file that exists but does not match the credential contract is a failed + // read. Preserve that distinction so callers do not report it as signed out. + throw new Error('Gemini CLI credentials file is invalid') } catch (err) { if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') { return null diff --git a/src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts b/src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts index fddd0a77d59..eda72c607f7 100644 --- a/src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts +++ b/src/main/rate-limits/gemini-usage-fetcher.fallback.test.ts @@ -153,6 +153,36 @@ describe('fetchGeminiRateLimits fallback oauth creds', () => { expect(result.weekly).toBeNull() }) + it('reports a present but malformed oauth_creds.json as a failed read', async () => { + readFileMock.mockImplementation(async (filePath: string) => { + if (filePath.includes('auth.json')) { + return JSON.stringify({}) + } + if (filePath.includes('oauth_creds.json')) { + return JSON.stringify({ + access_token: 42, + refresh_token: 'refresh', + expiry_date: Date.now() + }) + } + throw { code: 'ENOENT' } + }) + + const result = await fetchGeminiRateLimits(true) + + expect(result.status).toBe('error') + expect(result.error).toContain('credentials file is invalid') + }) + + it('keeps an actually missing oauth_creds.json as absent', async () => { + readFileMock.mockRejectedValue({ code: 'ENOENT' }) + + const result = await fetchGeminiRateLimits(true) + + expect(result.status).toBe('unavailable') + expect(result.error).toContain('credentials not found') + }) + it('returns error when loadCodeAssist cannot resolve a project for oauth_creds path', async () => { // Why: when the fallback (oauth_creds.json) path has no project embedded // and loadCodeAssist fails, we surface a clear "project ID not found"