From 760ef23b4fe991b3f62a822ddd210b6d4cf7f7e1 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 19 Sep 2025 11:16:40 +0000 Subject: [PATCH] fix: handle pending ask operations during checkpoint restore - Add handleWebviewAskResponse call before checkpoint restore to resolve any pending ask promises - This prevents "Current ask promise was ignored" errors when restoring checkpoints - Add comprehensive tests for pending ask scenarios Fixes #8177 --- .../checkpointRestoreHandler.spec.ts | 72 +++++++++++++++++++ src/core/webview/checkpointRestoreHandler.ts | 31 +++++--- 2 files changed, 92 insertions(+), 11 deletions(-) diff --git a/src/core/webview/__tests__/checkpointRestoreHandler.spec.ts b/src/core/webview/__tests__/checkpointRestoreHandler.spec.ts index 98773feb6c..241b3299ed 100644 --- a/src/core/webview/__tests__/checkpointRestoreHandler.spec.ts +++ b/src/core/webview/__tests__/checkpointRestoreHandler.spec.ts @@ -30,6 +30,8 @@ describe("checkpointRestoreHandler", () => { mockCline.abort = true }), checkpointRestore: vi.fn(), + handleWebviewAskResponse: vi.fn(), + lastMessageTs: undefined, clineMessages: [ { ts: 1, type: "user", say: "user", text: "First message" }, { ts: 2, type: "assistant", say: "assistant", text: "Response" }, @@ -238,5 +240,75 @@ describe("checkpointRestoreHandler", () => { "Error during checkpoint restore: Checkpoint restore failed", ) }) + + it("should handle pending ask operations before checkpoint restore", async () => { + // Simulate a pending ask operation + mockCline.lastMessageTs = Date.now() + + await handleCheckpointRestoreOperation({ + provider: mockProvider, + currentCline: mockCline, + messageTs: 3, + messageIndex: 2, + checkpoint: { hash: "abc123" }, + operation: "delete", + }) + + // Verify handleWebviewAskResponse was called to resolve pending ask + expect(mockCline.handleWebviewAskResponse).toHaveBeenCalledWith("messageResponse", "", undefined) + + // Verify checkpoint restore was still called + expect(mockCline.checkpointRestore).toHaveBeenCalled() + }) + + it("should handle pending ask operations for edit operations", async () => { + // Simulate a pending ask operation + mockCline.lastMessageTs = Date.now() + + const editData = { + editedContent: "Edited content", + images: [], + apiConversationHistoryIndex: 2, + } + + await handleCheckpointRestoreOperation({ + provider: mockProvider, + currentCline: mockCline, + messageTs: 3, + messageIndex: 2, + checkpoint: { hash: "abc123" }, + operation: "edit", + editData, + }) + + // Verify handleWebviewAskResponse was called to resolve pending ask + expect(mockCline.handleWebviewAskResponse).toHaveBeenCalledWith("messageResponse", "", undefined) + + // Verify checkpoint restore was still called + expect(mockCline.checkpointRestore).toHaveBeenCalled() + + // Verify pending edit operation was set + expect(mockProvider.setPendingEditOperation).toHaveBeenCalled() + }) + + it("should not call handleWebviewAskResponse if no pending ask", async () => { + // No pending ask operation (lastMessageTs is undefined) + mockCline.lastMessageTs = undefined + + await handleCheckpointRestoreOperation({ + provider: mockProvider, + currentCline: mockCline, + messageTs: 3, + messageIndex: 2, + checkpoint: { hash: "abc123" }, + operation: "delete", + }) + + // Verify handleWebviewAskResponse was NOT called + expect(mockCline.handleWebviewAskResponse).not.toHaveBeenCalled() + + // Verify checkpoint restore was still called + expect(mockCline.checkpointRestore).toHaveBeenCalled() + }) }) }) diff --git a/src/core/webview/checkpointRestoreHandler.ts b/src/core/webview/checkpointRestoreHandler.ts index a3f62f74f3..4bd07a6adb 100644 --- a/src/core/webview/checkpointRestoreHandler.ts +++ b/src/core/webview/checkpointRestoreHandler.ts @@ -27,18 +27,27 @@ export async function handleCheckpointRestoreOperation(config: CheckpointRestore const { provider, currentCline, messageTs, checkpoint, operation, editData } = config try { - // For delete operations, ensure the task is properly aborted to handle any pending ask operations + // For both delete and edit operations, we need to handle any pending ask operations // This prevents "Current ask promise was ignored" errors - // For edit operations, we don't abort because the checkpoint restore will handle it - if (operation === "delete" && currentCline && !currentCline.abort) { - currentCline.abortTask() - // Wait a bit for the abort to complete - await pWaitFor(() => currentCline.abort === true, { - timeout: 1000, - interval: 50, - }).catch(() => { - // Continue even if timeout - the abort flag should be set - }) + if (currentCline) { + // Handle any pending ask operations by providing a response + // This unblocks any waiting ask promises before the checkpoint restore + if (currentCline.lastMessageTs) { + // Use the public method to set a response for any pending ask + currentCline.handleWebviewAskResponse("messageResponse", "", undefined) + } + + // For delete operations, abort the task + if (operation === "delete" && !currentCline.abort) { + currentCline.abortTask() + // Wait a bit for the abort to complete + await pWaitFor(() => currentCline.abort === true, { + timeout: 1000, + interval: 50, + }).catch(() => { + // Continue even if timeout - the abort flag should be set + }) + } } // For edit operations, set up pending edit data before restoration