From 3da18c7712f834bbae58cbaeacceb32865aa5505 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sun, 18 Jan 2026 02:54:30 +0000 Subject: [PATCH] fix: process queued user messages during endless loop recovery (Issue #10814) When the model gets stuck in a repetitive loop (repeating the same tool call or not using tools), user messages sent during this time are queued but never processed because the model keeps receiving the same error feedback. This fix adds queue processing at two critical points: 1. In Task.ts when consecutiveNoToolUseCount >= 2: Check for queued messages and inject them as user feedback to break the loop. 2. In presentAssistantMessage.ts when tool repetition is detected: Check for queued messages before asking the user, allowing previously queued messages to provide the intervention needed. Both fixes reset the mistake counters when user guidance is received, giving the model a fresh start with the user-provided direction. Fixes #10814 --- .../presentAssistantMessage.ts | 48 ++++++++++++++----- src/core/task/Task.ts | 23 +++++++++ 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 693327a022e..42b509485d1 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -824,24 +824,48 @@ export async function presentAssistantMessage(cline: Task) { // If execution is not allowed, notify user and break. if (!repetitionCheck.allowExecution && repetitionCheck.askUser) { - // Handle repetition similar to mistake_limit_reached pattern. - const { response, text, images } = await cline.ask( - repetitionCheck.askUser.messageKey as ClineAsk, - repetitionCheck.askUser.messageDetail.replace("{toolName}", block.name), - ) + let feedbackText: string | undefined + let feedbackImages: string[] | undefined + + // CRITICAL FIX (GitHub #10814): Check for queued user messages first. + // When auto-approve is enabled and the model gets stuck in a loop, + // user messages are queued but never processed because the model + // keeps repeating the same tool call. By checking the queue here, + // we allow user intervention to break the loop. + if (!cline.messageQueueService.isEmpty()) { + const queuedMessage = cline.messageQueueService.dequeueMessage() + if (queuedMessage) { + feedbackText = queuedMessage.text + feedbackImages = queuedMessage.images + // Show the user's feedback in chat + await cline.say("user_feedback", feedbackText, feedbackImages) + } + } + + // If no queued message, ask the user for feedback + if (!feedbackText) { + const { response, text, images } = await cline.ask( + repetitionCheck.askUser.messageKey as ClineAsk, + repetitionCheck.askUser.messageDetail.replace("{toolName}", block.name), + ) - if (response === "messageResponse") { - // Add user feedback to userContent. + if (response === "messageResponse") { + feedbackText = text + feedbackImages = images + // Add user feedback to chat. + await cline.say("user_feedback", text, images) + } + } + + // If we have user feedback (from queue or ask), add it to userContent + if (feedbackText) { cline.userMessageContent.push( { type: "text" as const, - text: `Tool repetition limit reached. User feedback: ${text}`, + text: `Tool repetition limit reached. User feedback: ${feedbackText}`, }, - ...formatResponse.imageBlocks(images), + ...formatResponse.imageBlocks(feedbackImages), ) - - // Add user feedback to chat. - await cline.say("user_feedback", text, images) } // Track tool repetition in telemetry via PostHog exception tracking and event. diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 3acb6c24918..7dc85ec9784 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -3540,6 +3540,29 @@ export class Task extends EventEmitter implements TaskLike { await this.say("error", "MODEL_NO_TOOLS_USED") // Only count toward mistake limit after second consecutive failure this.consecutiveMistakeCount++ + + // CRITICAL: Check for queued user messages when the model appears stuck. + // This allows user intervention to break out of endless loops where the model + // keeps trying the same approach without using tools (GitHub issue #10814). + if (!this.messageQueueService.isEmpty()) { + const queuedMessage = this.messageQueueService.dequeueMessage() + if (queuedMessage) { + // Add user's message as feedback to break the loop + await this.say("user_feedback", queuedMessage.text, queuedMessage.images) + this.userMessageContent.push({ + type: "text", + text: formatResponse.tooManyMistakes(queuedMessage.text), + }) + if (queuedMessage.images && queuedMessage.images.length > 0) { + this.userMessageContent.push( + ...formatResponse.imageBlocks(queuedMessage.images), + ) + } + // Reset mistake counter since user provided guidance + this.consecutiveMistakeCount = 0 + this.consecutiveNoToolUseCount = 0 + } + } } // Use the task's locked protocol for consistent behavior