From 020a89945f1129b5babeb90c039130e7f5936ad4 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 18 Jul 2025 15:32:02 +0000 Subject: [PATCH] fix: distinguish API failures from user cancellations - Add abortReason field to Task class to track cancellation source - Update abortTask method to accept reason parameter (user vs api_failure) - Fix cancellation reason logic to use abortReason instead of abort flag - Update ClineProvider calls to specify user cancellation reason - Resolves issue where API failures were incorrectly reported as user cancellations Fixes #5895 --- src/core/task/Task.ts | 17 +++++++++-------- src/core/webview/ClineProvider.ts | 4 ++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 53b8ef5b87d..cf324ce8051 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -140,6 +140,7 @@ export class Task extends EventEmitter { providerRef: WeakRef private readonly globalStoragePath: string abort: boolean = false + abortReason: 'user' | 'api_failure' | null = null didFinishAbortingStream = false abandoned = false isInitialized = false @@ -1074,8 +1075,8 @@ export class Task extends EventEmitter { } } - public async abortTask(isAbandoned = false) { - console.log(`[subtasks] aborting task ${this.taskId}.${this.instanceId}`) + public async abortTask(isAbandoned = false, reason: 'user' | 'api_failure' = 'user') { + console.log(`[subtasks] aborting task ${this.taskId}.${this.instanceId} with reason: ${reason}`) // Will stop any autonomously running promises. if (isAbandoned) { @@ -1083,6 +1084,7 @@ export class Task extends EventEmitter { } this.abort = true + this.abortReason = reason this.emit("taskAborted") try { @@ -1442,13 +1444,12 @@ export class Task extends EventEmitter { // could be in (i.e. could have streamed some tools the user // may have executed), so we just resort to replicating a // cancel task. - this.abortTask() + this.abortTask(false, 'api_failure') - // Check if this was a user-initiated cancellation - // If this.abort is true, it means the user clicked cancel, so we should - // treat this as "user_cancelled" rather than "streaming_failed" - const cancelReason = this.abort ? "user_cancelled" : "streaming_failed" - const streamingFailedMessage = this.abort + // Check if this was a user-initiated cancellation or an API failure + // Use the abortReason to properly distinguish between user cancellations and API failures + const cancelReason = this.abortReason === 'user' ? "user_cancelled" : "streaming_failed" + const streamingFailedMessage = this.abortReason === 'user' ? undefined : (error.message ?? JSON.stringify(serializeError(error), null, 2)) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 107122dcb46..17225061949 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -192,7 +192,7 @@ export class ClineProvider try { // Abort the running task and set isAbandoned to true so // all running promises will exit as well. - await cline.abortTask(true) + await cline.abortTask(true, 'user') } catch (e) { this.log( `[subtasks] encountered error while aborting task ${cline.taskId}.${cline.instanceId}: ${e.message}`, @@ -974,7 +974,7 @@ export class ClineProvider const rootTask = cline.rootTask const parentTask = cline.parentTask - cline.abortTask() + cline.abortTask(false, 'user') await pWaitFor( () =>