From b9d1e2826e547fb3271574bcc0243828fa99e55c Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Thu, 13 Nov 2025 16:50:12 -0500 Subject: [PATCH 1/2] fix: prevent consecutive user messages on streaming retry When a streaming error occurs after a tool use, the retry logic was adding a duplicate user message to the API conversation history, causing 400 errors. The fix skips adding the user message on retry attempts since it was already added in the first attempt. --- src/core/task/Task.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index c9a83140301..432908223a9 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1867,8 +1867,13 @@ export class Task extends EventEmitter implements TaskLike { // results. const finalUserContent = [...parsedUserContent, { type: "text" as const, text: environmentDetails }] - await this.addToApiConversationHistory({ role: "user", content: finalUserContent }) - TelemetryService.instance.captureConversationMessage(this.taskId, "user") + // Only add user message to conversation history if this is NOT a retry attempt + // On retries, the user message was already added in the previous attempt + // This prevents consecutive user messages (including tool->user sequences) which cause 400 errors + if ((currentItem.retryAttempt ?? 0) === 0) { + await this.addToApiConversationHistory({ role: "user", content: finalUserContent }) + TelemetryService.instance.captureConversationMessage(this.taskId, "user") + } // Since we sent off a placeholder api_req_started message to update the // webview while waiting to actually start the API request (to load From add82fefc9854c531dcccd0aab9aeb759aaea898 Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Thu, 13 Nov 2025 18:14:20 -0500 Subject: [PATCH 2/2] fix: handle user message re-addition for empty response retries The previous fix prevented adding user messages on retry attempts to avoid consecutive user messages. However, when the assistant returns an empty response, the user message is removed from conversation history (line 2534). On retry, the fix would skip re-adding the message, leaving the conversation incomplete. Solution: Track when the user message was removed and re-add it on retry only in that specific case, while still preventing duplicate messages in normal mid-stream retry scenarios. --- src/core/task/Task.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 432908223a9..388bcff6a61 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1767,6 +1767,7 @@ export class Task extends EventEmitter implements TaskLike { userContent: Anthropic.Messages.ContentBlockParam[] includeFileDetails: boolean retryAttempt?: number + userMessageWasRemoved?: boolean // Track if user message was removed due to empty response } const stack: StackItem[] = [{ userContent, includeFileDetails, retryAttempt: 0 }] @@ -1867,10 +1868,11 @@ export class Task extends EventEmitter implements TaskLike { // results. const finalUserContent = [...parsedUserContent, { type: "text" as const, text: environmentDetails }] - // Only add user message to conversation history if this is NOT a retry attempt - // On retries, the user message was already added in the previous attempt - // This prevents consecutive user messages (including tool->user sequences) which cause 400 errors - if ((currentItem.retryAttempt ?? 0) === 0) { + // Only add user message to conversation history if: + // 1. This is the first attempt (retryAttempt === 0), OR + // 2. The message was removed in a previous iteration (userMessageWasRemoved === true) + // This prevents consecutive user messages while allowing re-add when needed + if ((currentItem.retryAttempt ?? 0) === 0 || currentItem.userMessageWasRemoved) { await this.addToApiConversationHistory({ role: "user", content: finalUserContent }) TelemetryService.instance.captureConversationMessage(this.taskId, "user") } @@ -2557,10 +2559,12 @@ export class Task extends EventEmitter implements TaskLike { } // Push the same content back onto the stack to retry, incrementing the retry attempt counter + // Mark that user message was removed so it gets re-added on retry stack.push({ userContent: currentUserContent, includeFileDetails: false, retryAttempt: (currentItem.retryAttempt ?? 0) + 1, + userMessageWasRemoved: true, }) // Continue to retry the request