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
46 changes: 17 additions & 29 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ import { AutoApprovalHandler, checkAutoApproval } from "../auto-approval"
import { MessageManager } from "../message-manager"
import { validateAndFixToolResultIds } from "./validateToolResultIds"
import { mergeConsecutiveApiMessages } from "./mergeConsecutiveApiMessages"
import { appendEnvironmentDetails, removeEnvironmentDetailsBlocks } from "./appendEnvironmentDetails"

const MAX_EXPONENTIAL_BACKOFF_SECONDS = 600 // 10 minutes
const DEFAULT_USAGE_COLLECTION_TIMEOUT_MS = 5000 // 5 seconds
Expand Down Expand Up @@ -2568,20 +2569,18 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
if (lastUserMsgIndex >= 0) {
const lastUserMsg = this.apiConversationHistory[lastUserMsgIndex]
if (Array.isArray(lastUserMsg.content)) {
// Remove any existing environment_details blocks before adding fresh ones
const contentWithoutEnvDetails = lastUserMsg.content.filter(
(block: Anthropic.Messages.ContentBlockParam) => {
if (block.type === "text" && typeof block.text === "string") {
const isEnvironmentDetailsBlock =
block.text.trim().startsWith("<environment_details>") &&
block.text.trim().endsWith("</environment_details>")
return !isEnvironmentDetailsBlock
}
return true
},
// Remove any existing environment_details blocks before adding fresh ones,
// then append env details to the last text or tool_result block.
// This avoids creating standalone trailing text blocks which can break
// interleaved-thinking models like DeepSeek reasoner.
const contentWithoutEnvDetails = removeEnvironmentDetailsBlocks(
lastUserMsg.content as (
| Anthropic.Messages.TextBlockParam
| Anthropic.Messages.ImageBlockParam
| Anthropic.Messages.ToolResultBlockParam
)[],
)
// Add fresh environment details
lastUserMsg.content = [...contentWithoutEnvDetails, { type: "text" as const, text: environmentDetails }]
lastUserMsg.content = appendEnvironmentDetails(contentWithoutEnvDetails, environmentDetails)
Comment on lines +2576 to +2583
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removeEnvironmentDetailsBlocks only strips standalone text blocks that are entirely <environment_details>...</environment_details>. Now that env details are appended into existing blocks, this function won't find them on a second pass through the same content. Concretely, in the delegation resume path here: after the first call, the last user message in history has env details appended (e.g. "Tool result\n\n<environment_details>...") -- removeEnvironmentDetailsBlocks won't detect that, so appendEnvironmentDetails adds a second copy.

stripAppendedEnvironmentDetails was written to handle exactly this (it calls removeEnvironmentDetailsBlocks and regex-strips appended env details from the last block), but it's only tested and never used in production. Both this call site and the one at line 2750 should use stripAppendedEnvironmentDetails instead.

Fix it with Roo Code or mention @roomote and request a fix.

}
}

Expand Down Expand Up @@ -2748,23 +2747,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
// Remove any existing environment_details blocks before adding fresh ones.
// This prevents duplicate environment details when resuming tasks,
// where the old user message content may already contain environment details from the previous session.
// We check for both opening and closing tags to ensure we're matching complete environment detail blocks,
// not just mentions of the tag in regular content.
const contentWithoutEnvDetails = parsedUserContent.filter((block) => {
if (block.type === "text" && typeof block.text === "string") {
// Check if this text block is a complete environment_details block
// by verifying it starts with the opening tag and ends with the closing tag
const isEnvironmentDetailsBlock =
block.text.trim().startsWith("<environment_details>") &&
block.text.trim().endsWith("</environment_details>")
return !isEnvironmentDetailsBlock
}
return true
})
const contentWithoutEnvDetails = removeEnvironmentDetailsBlocks(parsedUserContent)

// Add environment details as its own text block, separate from tool
// results.
let finalUserContent = [...contentWithoutEnvDetails, { type: "text" as const, text: environmentDetails }]
// Append environment details to the last text or tool_result block.
// This avoids creating standalone trailing text blocks which can break
// interleaved-thinking models like DeepSeek reasoner that expect specific message shapes.
let finalUserContent = appendEnvironmentDetails(contentWithoutEnvDetails, environmentDetails)
// Only add user message to conversation history if:
// 1. This is the first attempt (retryAttempt === 0), AND
// 2. The original userContent was not empty (empty signals delegation resume where
Expand Down
Loading
Loading