From 5c11f205381cea7c1b45d283c13288e75480a98b Mon Sep 17 00:00:00 2001 From: hannesrudolph Date: Thu, 19 Jun 2025 15:44:03 -0600 Subject: [PATCH] fix: resolve orchestrator subtask cancellation during API streaming error (#4896) - Add setTimeout to prevent race condition when aborting streams - Enhance error logging with parent task ID for better debugging - Improve error handling to ensure parent task resumption - Make resumePausedTask more resilient to aborted states Fixes #4896 --- src/core/task/Task.ts | 11 ++++++++++- src/core/webview/webviewMessageHandler.ts | 16 +++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 6f6f2d684a..42417637cb 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -758,6 +758,14 @@ export class Task extends EventEmitter { // this is the result of what it has done add the message to the chat // history and to the webview ui. try { + // Check if the task is aborted or abandoned before trying to add messages + if (this.abort || this.abandoned) { + this.providerRef + .deref() + ?.log(`[subtasks] Parent task ${this.taskId} is aborted/abandoned, skipping resume message`) + return + } + await this.say("subtask_result", lastMessage) await this.addToApiConversationHistory({ @@ -769,7 +777,8 @@ export class Task extends EventEmitter { .deref() ?.log(`Error failed to add reply from subtask into conversation of parent task, error: ${error}`) - throw error + // Don't throw the error - we still want the parent task to be unpaused + // even if we couldn't add the message } } diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 720e5cda0d..596c34fa4e 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -204,7 +204,21 @@ export const webviewMessageHandler = async ( // Check if the current task actually has a parent task const currentTask = provider.getCurrentCline() if (currentTask && currentTask.parentTask) { - await provider.finishSubTask(t("common:tasks.canceled")) + // For subtasks, we need to ensure the parent task is resumed even if the subtask is in an error state + try { + // First abort the current subtask if it's still running + if (currentTask.isStreaming || !currentTask.didFinishAbortingStream) { + currentTask.abortTask() + // Small delay to ensure abort signal is processed before cleanup + // This prevents race conditions where the stream might still be processing + await new Promise((resolve) => setTimeout(resolve, 100)) + } + await provider.finishSubTask(t("common:tasks.canceled")) + } catch (error) { + // If there's an error, still try to resume the parent task + provider.log(`Error during subtask cancellation: ${error}`) + await provider.finishSubTask(t("common:tasks.canceled")) + } } else { // Regular task - just clear it await provider.clearTask()