From b284eddf40fe992752c18d8e538f850f4a70b594 Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:12:30 -0400 Subject: [PATCH] fix: prevent infinite loop when canceling during auto-retry (#8902) * fix: prevent infinite loop when canceling during auto-retry - Add abort check after backoffAndAnnounce in first-chunk retry logic - Add abort check after backoffAndAnnounce in mid-stream retry logic - Properly handle task abortion to break retry loops Fixes #8901 * docs: add critical comments explaining abort checks - Document the importance of abort checks after backoff - Explain how these checks prevent infinite loops - Add context for future maintainability --------- Co-authored-by: Roo Code --- src/core/task/Task.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 51799b742e..8eb15441bd 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)