diff --git a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx index 42f28f5e2..c4a5a6117 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx +++ b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx @@ -2526,6 +2526,77 @@ describe('useGeminiStream', () => { expect.any(String), ); }); + + it('should clear static error when starting a new query', async () => { + // First, mock a stream that yields an error (static error without countdown) + mockSendMessageStream.mockReturnValueOnce( + (async function* () { + yield { + type: ServerGeminiEventType.Error, + value: { error: { message: 'First error' } }, + }; + })(), + ); + + const { result } = renderHook(() => + useGeminiStream( + new MockedGeminiClientClass(mockConfig), + [], + mockAddItem, + mockConfig, + mockLoadedSettings, + mockOnDebugMessage, + mockHandleSlashCommand, + false, + () => 'vscode' as EditorType, + () => {}, + () => Promise.resolve(), + false, + () => {}, + () => {}, + () => {}, + () => {}, + 80, + 24, + ), + ); + + // Submit first query that will fail + await act(async () => { + await result.current.submitQuery('First query'); + }); + + // Verify error appears in pending history items + await waitFor(() => { + const errorItem = result.current.pendingHistoryItems.find( + (item) => item.type === 'error', + ); + expect(errorItem).toBeDefined(); + }); + + // Now mock a successful stream for the second query + mockSendMessageStream.mockReturnValueOnce( + (async function* () { + yield { + type: ServerGeminiEventType.Text, + value: 'Success response', + }; + })(), + ); + + // Submit second query + await act(async () => { + await result.current.submitQuery('Second query'); + }); + + // Verify the error is cleared (no longer in pending history items) + await waitFor(() => { + const errorItem = result.current.pendingHistoryItems.find( + (item) => item.type === 'error', + ); + expect(errorItem).toBeUndefined(); + }); + }); }); describe('Concurrent Execution Prevention', () => { diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts index 0e5f29216..6af847997 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.ts +++ b/packages/cli/src/ui/hooks/useGeminiStream.ts @@ -1080,8 +1080,13 @@ export const useGeminiStream = ( if (!options?.isContinuation) { setModelSwitchedFromQuotaError(false); // Commit any pending retry error to history (without hint) since the - // user is starting a new conversation turn - if (pendingRetryCountdownItemRef.current) { + // user is starting a new conversation turn. + // Clear both countdown-based errors AND static errors (those without + // an active countdown timer, e.g. "Press Ctrl+Y to retry"). + if ( + pendingRetryCountdownItemRef.current || + pendingRetryErrorItemRef.current + ) { clearRetryCountdown(); } } @@ -1176,7 +1181,8 @@ export const useGeminiStream = ( } // Only clear auto-retry countdown errors (those with an active timer). // Do NOT clear static error+hint from handleErrorEvent — those should - // remain visible until the user presses Ctrl+Y to retry. + // remain visible until the user presses Ctrl+Y to retry or starts + // a new conversation turn (cleared in submitQuery). if (retryCountdownTimerRef.current) { clearRetryCountdown(); } @@ -1223,6 +1229,7 @@ export const useGeminiStream = ( handleLoopDetectedEvent, clearRetryCountdown, pendingRetryCountdownItemRef, + pendingRetryErrorItemRef, setPendingRetryErrorItem, ], );