Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 36 additions & 12 deletions src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 23 additions & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3540,6 +3540,29 @@ export class Task extends EventEmitter<TaskEvents> 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
Expand Down
Loading