diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 51799b742ece..8eb15441bd88 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -2240,6 +2240,17 @@ export class Task extends EventEmitter implements TaskLike { error, streamingFailedMessage, ) + + // Check if task was aborted during the backoff + if (this.abort) { + console.log( + `[Task#${this.taskId}.${this.instanceId}] Task aborted during mid-stream retry backoff`, + ) + // Abort the entire task + this.abortReason = "user_cancelled" + await this.abortTask() + break + } } // Push the same content back onto the stack to retry, incrementing the retry attempt counter @@ -2790,6 +2801,15 @@ export class Task extends EventEmitter implements TaskLike { // Apply shared exponential backoff and countdown UX await this.backoffAndAnnounce(retryAttempt, error, errorMsg) + // CRITICAL: Check if task was aborted during the backoff countdown + // This prevents infinite loops when users cancel during auto-retry + // Without this check, the recursive call below would continue even after abort + if (this.abort) { + throw new Error( + `[Task#attemptApiRequest] task ${this.taskId}.${this.instanceId} aborted during retry`, + ) + } + // Delegate generator output from the recursive call with // incremented retry count. yield* this.attemptApiRequest(retryAttempt + 1)