fix: cancel backend auto-approval timeout when auto-approve is toggled off mid-countdown (#11439)

Co-authored-by: Sannidhya <sann@Sannidhyas-MacBook-Pro.local>
This commit is contained in:
SannidhyaSah 2026-02-12 21:08:11 +05:30 committed by GitHub
parent cdf481c8f9
commit 6c9ff49dd8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 106 additions and 0 deletions

View file

@ -1442,6 +1442,12 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
vscode.postMessage({ type: "askResponse", askResponse: "objectResponse", text: JSON.stringify(response) })
}, [])
// Cancel backend auto-approval timeout when FollowUpSuggest's countdown effect cleans up.
// This is called when auto-approve is toggled off, a suggestion is clicked, or the component unmounts.
const handleFollowUpUnmount = useCallback(() => {
vscode.postMessage({ type: "cancelAutoApproval" })
}, [])
const itemContent = useCallback(
(index: number, messageOrGroup: ClineMessage) => {
const hasCheckpoint = modifiedMessages.some((message) => message.say === "checkpoint_saved")
@ -1459,6 +1465,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
isStreaming={isStreaming}
onSuggestionClick={handleSuggestionClickInRow} // This was already stabilized
onBatchFileResponse={handleBatchFileResponse}
onFollowUpUnmount={handleFollowUpUnmount}
isFollowUpAnswered={messageOrGroup.isAnswered === true || messageOrGroup.ts === currentFollowUpTs}
isFollowUpAutoApprovalPaused={isFollowUpAutoApprovalPaused}
editable={
@ -1489,6 +1496,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
isStreaming,
handleSuggestionClickInRow,
handleBatchFileResponse,
handleFollowUpUnmount,
currentFollowUpTs,
isFollowUpAutoApprovalPaused,
enableButtons,

View file

@ -592,4 +592,102 @@ describe("FollowUpSuggest", () => {
expect(screen.getByText(/3s/)).toBeInTheDocument()
})
})
describe("auto-approve toggle off mid-countdown", () => {
it("should call onCancelAutoApproval when autoApprovalEnabled changes to false during countdown", async () => {
const { rerender } = renderWithTestProviders(
<FollowUpSuggest
suggestions={mockSuggestions}
onSuggestionClick={mockOnSuggestionClick}
ts={123}
onCancelAutoApproval={mockOnCancelAutoApproval}
isAnswered={false}
/>,
defaultTestState,
)
// Should show countdown initially
expect(screen.getByText(/3s/)).toBeInTheDocument()
// Advance timer partially
await act(async () => {
vi.advanceTimersByTime(1000)
})
// Countdown should be at 2s
expect(screen.getByText(/2s/)).toBeInTheDocument()
// Clear mock to track calls from the toggle-off
mockOnCancelAutoApproval.mockClear()
// User toggles auto-approve off
rerender(
<TestExtensionStateProvider value={{ ...defaultTestState, autoApprovalEnabled: false }}>
<TooltipProvider>
<FollowUpSuggest
suggestions={mockSuggestions}
onSuggestionClick={mockOnSuggestionClick}
ts={123}
onCancelAutoApproval={mockOnCancelAutoApproval}
isAnswered={false}
/>
</TooltipProvider>
</TestExtensionStateProvider>,
)
// Countdown should disappear
expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument()
// onCancelAutoApproval should have been called to cancel the backend timeout
expect(mockOnCancelAutoApproval).toHaveBeenCalled()
// Advance timer past original timeout - nothing should happen
await act(async () => {
vi.advanceTimersByTime(5000)
})
// onSuggestionClick should NOT have been called
expect(mockOnSuggestionClick).not.toHaveBeenCalled()
})
it("should call onCancelAutoApproval when alwaysAllowFollowupQuestions changes to false during countdown", async () => {
const { rerender } = renderWithTestProviders(
<FollowUpSuggest
suggestions={mockSuggestions}
onSuggestionClick={mockOnSuggestionClick}
ts={123}
onCancelAutoApproval={mockOnCancelAutoApproval}
isAnswered={false}
/>,
defaultTestState,
)
// Should show countdown initially
expect(screen.getByText(/3s/)).toBeInTheDocument()
// Clear mock to track calls from the toggle-off
mockOnCancelAutoApproval.mockClear()
// User disables follow-up question auto-approval
rerender(
<TestExtensionStateProvider value={{ ...defaultTestState, alwaysAllowFollowupQuestions: false }}>
<TooltipProvider>
<FollowUpSuggest
suggestions={mockSuggestions}
onSuggestionClick={mockOnSuggestionClick}
ts={123}
onCancelAutoApproval={mockOnCancelAutoApproval}
isAnswered={false}
/>
</TooltipProvider>
</TestExtensionStateProvider>,
)
// Countdown should disappear
expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument()
// onCancelAutoApproval should have been called to cancel the backend timeout
expect(mockOnCancelAutoApproval).toHaveBeenCalled()
})
})
})