mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-29 12:11:09 +00:00
fix: clear retry error messages promptly after auto-retry succeeds
Previously, when an auto-retry countdown elapsed and the server sent a Retry event (without retryInfo) to signal the actual retry attempt, the error message was not cleared because `pendingRetryCountdownItemRef` was still set. This caused stale error messages to persist in the UI until the user manually initiated a new request. Additionally, when the user pressed Ctrl+Y to retry, the error was committed to history (without hint) instead of being discarded. This was inconsistent with the auto-retry behavior where errors are silently cleared on success. Changes: - Always call clearRetryCountdown() when a Retry event without retryInfo is received, removing the flawed guard condition - Remove error-to-history commit in retryLastPrompt for consistent UX - Add test covering the countdown-elapsed retry scenario Closes #2310 Made-with: Cursor
This commit is contained in:
parent
e181cfc097
commit
d951e30cfa
2 changed files with 110 additions and 14 deletions
|
|
@ -2244,6 +2244,7 @@ describe('useGeminiStream', () => {
|
|||
it('should show a retry countdown and update pending history over time', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
let continueToRetryAttempt: (() => void) | undefined;
|
||||
let resolveStream: (() => void) | undefined;
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
|
|
@ -2256,6 +2257,9 @@ describe('useGeminiStream', () => {
|
|||
delayMs: 3000,
|
||||
},
|
||||
};
|
||||
await new Promise<void>((resolve) => {
|
||||
continueToRetryAttempt = resolve;
|
||||
});
|
||||
yield {
|
||||
type: ServerGeminiEventType.Retry,
|
||||
};
|
||||
|
|
@ -2330,6 +2334,12 @@ describe('useGeminiStream', () => {
|
|||
'2s',
|
||||
);
|
||||
|
||||
continueToRetryAttempt?.();
|
||||
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
resolveStream?.();
|
||||
|
||||
await act(async () => {
|
||||
|
|
@ -2347,6 +2357,103 @@ describe('useGeminiStream', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('should clear retry errors after auto-retry succeeds once the countdown has elapsed', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
let continueAfterCountdown: (() => void) | undefined;
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
type: ServerGeminiEventType.Retry,
|
||||
retryInfo: {
|
||||
message: '[API Error: Rate limit exceeded]',
|
||||
attempt: 1,
|
||||
maxRetries: 3,
|
||||
delayMs: 1000,
|
||||
},
|
||||
};
|
||||
await new Promise<void>((resolve) => {
|
||||
continueAfterCountdown = resolve;
|
||||
});
|
||||
yield {
|
||||
type: ServerGeminiEventType.Retry,
|
||||
};
|
||||
yield {
|
||||
type: ServerGeminiEventType.Text,
|
||||
value: 'Success after retry',
|
||||
};
|
||||
yield {
|
||||
type: ServerGeminiEventType.Finished,
|
||||
value: { reason: 'STOP', usageMetadata: undefined },
|
||||
};
|
||||
})(),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useGeminiStream(
|
||||
new MockedGeminiClientClass(mockConfig),
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockLoadedSettings,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => 'vscode' as EditorType,
|
||||
() => {},
|
||||
() => Promise.resolve(),
|
||||
false,
|
||||
() => {},
|
||||
() => {},
|
||||
() => {},
|
||||
() => {},
|
||||
80,
|
||||
24,
|
||||
),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
void result.current.submitQuery('Trigger retry after countdown');
|
||||
});
|
||||
|
||||
let errorItem = result.current.pendingHistoryItems.find(
|
||||
(item) => item.type === MessageType.ERROR,
|
||||
) as { hint?: string } | undefined;
|
||||
for (let attempts = 0; attempts < 5 && !errorItem; attempts++) {
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
errorItem = result.current.pendingHistoryItems.find(
|
||||
(item) => item.type === MessageType.ERROR,
|
||||
) as { hint?: string } | undefined;
|
||||
}
|
||||
expect(errorItem?.hint).toContain('1s');
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
});
|
||||
|
||||
const staleErrorBeforeRetryCompletes =
|
||||
result.current.pendingHistoryItems.find(
|
||||
(item) => item.type === MessageType.ERROR,
|
||||
) as { hint?: string } | undefined;
|
||||
expect(staleErrorBeforeRetryCompletes?.hint).toContain('0s');
|
||||
|
||||
await act(async () => {
|
||||
continueAfterCountdown?.();
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const remainingError = result.current.pendingHistoryItems.find(
|
||||
(item) => item.type === MessageType.ERROR,
|
||||
);
|
||||
expect(remainingError).toBeUndefined();
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('should memoize pendingHistoryItems', () => {
|
||||
mockUseReactToolScheduler.mockReturnValue([
|
||||
[],
|
||||
|
|
|
|||
|
|
@ -1034,7 +1034,8 @@ export const useGeminiStream = (
|
|||
// Show retry info if available (rate-limit / throttling errors)
|
||||
if (event.retryInfo) {
|
||||
startRetryCountdown(event.retryInfo);
|
||||
} else if (!pendingRetryCountdownItemRef.current) {
|
||||
} else {
|
||||
// The retry attempt is starting now, so any prior retry UI is stale.
|
||||
clearRetryCountdown();
|
||||
}
|
||||
break;
|
||||
|
|
@ -1075,7 +1076,6 @@ export const useGeminiStream = (
|
|||
setThought,
|
||||
pendingHistoryItemRef,
|
||||
setPendingHistoryItem,
|
||||
pendingRetryCountdownItemRef,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
@ -1301,24 +1301,13 @@ export const useGeminiStream = (
|
|||
return;
|
||||
}
|
||||
|
||||
// Commit the error to history (without hint) before clearing
|
||||
const errorItem = pendingRetryErrorItemRef.current;
|
||||
if (errorItem) {
|
||||
addItem({ type: errorItem.type, text: errorItem.text }, Date.now());
|
||||
}
|
||||
clearRetryCountdown();
|
||||
|
||||
await submitQuery(lastPrompt, {
|
||||
isContinuation: false,
|
||||
skipPreparation: true,
|
||||
});
|
||||
}, [
|
||||
streamingState,
|
||||
addItem,
|
||||
clearRetryCountdown,
|
||||
submitQuery,
|
||||
pendingRetryErrorItemRef,
|
||||
]);
|
||||
}, [streamingState, addItem, clearRetryCountdown, submitQuery]);
|
||||
|
||||
const handleApprovalModeChange = useCallback(
|
||||
async (newApprovalMode: ApprovalMode) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue