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
This commit is contained in:
hannesrudolph 2025-06-19 15:44:03 -06:00
parent db334c182f
commit 5c11f20538
2 changed files with 25 additions and 2 deletions

View file

@ -758,6 +758,14 @@ export class Task extends EventEmitter<ClineEvents> {
// 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<ClineEvents> {
.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
}
}

View file

@ -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()