diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index abf92108c3..fe5ebd1fad 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -433,6 +433,7 @@ Logging in with Google... Please restart Gemini CLI to continue. const handleApiKeySubmit = useCallback( async (apiKey: string) => { try { + onAuthError(null); if (!apiKey.trim() && apiKey.length > 1) { onAuthError( 'API key cannot be empty string with length greater than 1.', diff --git a/packages/core/src/core/apiKeyCredentialStorage.test.ts b/packages/core/src/core/apiKeyCredentialStorage.test.ts index 703884c6aa..b0b0551f4b 100644 --- a/packages/core/src/core/apiKeyCredentialStorage.test.ts +++ b/packages/core/src/core/apiKeyCredentialStorage.test.ts @@ -79,4 +79,10 @@ describe('ApiKeyCredentialStorage', () => { await clearApiKey(); expect(deleteCredentialsMock).toHaveBeenCalledWith('default-api-key'); }); + + it('should not throw when clearing an API key fails', async () => { + deleteCredentialsMock.mockRejectedValueOnce(new Error('Failed to delete')); + await expect(saveApiKey('')).resolves.not.toThrow(); + expect(deleteCredentialsMock).toHaveBeenCalledWith('default-api-key'); + }); }); diff --git a/packages/core/src/core/apiKeyCredentialStorage.ts b/packages/core/src/core/apiKeyCredentialStorage.ts index 691ce949d2..4836ba075b 100644 --- a/packages/core/src/core/apiKeyCredentialStorage.ts +++ b/packages/core/src/core/apiKeyCredentialStorage.ts @@ -26,15 +26,6 @@ export async function loadApiKey(): Promise { return null; } catch (error: unknown) { - // Ignore "file not found" error from FileTokenStorage, it just means no key is saved yet. - // This is common in fresh environments like e2e tests. - if ( - error instanceof Error && - error.message === 'Token file does not exist' - ) { - return null; - } - // Log other errors but don't crash, just return null so user can re-enter key debugLogger.error('Failed to load API key from storage:', error); return null; @@ -48,7 +39,12 @@ export async function saveApiKey( apiKey: string | null | undefined, ): Promise { if (!apiKey || apiKey.trim() === '') { - await storage.deleteCredentials(DEFAULT_API_KEY_ENTRY); + try { + await storage.deleteCredentials(DEFAULT_API_KEY_ENTRY); + } catch (error: unknown) { + // Ignore errors when deleting, as it might not exist + debugLogger.warn('Failed to delete API key from storage:', error); + } return; } diff --git a/packages/core/src/mcp/token-storage/file-token-storage.test.ts b/packages/core/src/mcp/token-storage/file-token-storage.test.ts index fcaebaf508..48050f37e6 100644 --- a/packages/core/src/mcp/token-storage/file-token-storage.test.ts +++ b/packages/core/src/mcp/token-storage/file-token-storage.test.ts @@ -58,12 +58,11 @@ describe('FileTokenStorage', () => { }); describe('getCredentials', () => { - it('should throw error when file does not exist', async () => { + it('should return null when file does not exist', async () => { mockFs.readFile.mockRejectedValue({ code: 'ENOENT' }); - await expect(storage.getCredentials('test-server')).rejects.toThrow( - 'Token file does not exist', - ); + const result = await storage.getCredentials('test-server'); + expect(result).toBeNull(); }); it('should return null for expired tokens', async () => { @@ -179,7 +178,7 @@ describe('FileTokenStorage', () => { mockFs.readFile.mockRejectedValue({ code: 'ENOENT' }); await expect(storage.deleteCredentials('test-server')).rejects.toThrow( - 'Token file does not exist', + 'No credentials found for test-server', ); }); @@ -246,12 +245,11 @@ describe('FileTokenStorage', () => { }); describe('listServers', () => { - it('should throw error when file does not exist', async () => { + it('should return empty list when file does not exist', async () => { mockFs.readFile.mockRejectedValue({ code: 'ENOENT' }); - await expect(storage.listServers()).rejects.toThrow( - 'Token file does not exist', - ); + const result = await storage.listServers(); + expect(result).toEqual([]); }); it('should return list of server names', async () => { diff --git a/packages/core/src/mcp/token-storage/file-token-storage.ts b/packages/core/src/mcp/token-storage/file-token-storage.ts index 4da1d5935d..a9e17b60da 100644 --- a/packages/core/src/mcp/token-storage/file-token-storage.ts +++ b/packages/core/src/mcp/token-storage/file-token-storage.ts @@ -77,7 +77,7 @@ export class FileTokenStorage extends BaseTokenStorage { } catch (error: unknown) { const err = error as NodeJS.ErrnoException & { message?: string }; if (err.code === 'ENOENT') { - throw new Error('Token file does not exist'); + return new Map(); } if ( err.message?.includes('Invalid encrypted data format') || diff --git a/packages/core/src/mcp/token-storage/keychain-token-storage.ts b/packages/core/src/mcp/token-storage/keychain-token-storage.ts index 93e94acb29..b8ea7b689b 100644 --- a/packages/core/src/mcp/token-storage/keychain-token-storage.ts +++ b/packages/core/src/mcp/token-storage/keychain-token-storage.ts @@ -46,8 +46,8 @@ export class KeychainTokenStorage const moduleName = 'keytar'; const module = await import(moduleName); this.keytarModule = module.default || module; - } catch (error) { - coreEvents.emitFeedback('error', "Failed to load 'keytar' module", error); + } catch (_) { + //Keytar is optional so we shouldn't raise an error of log anything. } return this.keytarModule; }