mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-10 22:41:14 +00:00
fix: improve write_to_file missing content error with actionable recovery hints
When write_to_file is called without the content parameter (typically due to output token truncation on large files), the error feedback sent back to the model was generic and unhelpful. The model would simply retry the same failing approach, hitting the same token limit each time. This change replaces the generic "missing parameter" error with a specific message that: - Explains the likely cause (output token truncation) - Suggests concrete alternatives (create smaller file first, use edit_file, split into multiple files) - Explicitly tells the model NOT to retry the same approach Closes #11795
This commit is contained in:
parent
193053110b
commit
1f936e47c8
3 changed files with 83 additions and 1 deletions
|
|
@ -66,6 +66,21 @@ Otherwise, if you have not completed the task and do not need additional informa
|
|||
return `Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${instructions}`
|
||||
},
|
||||
|
||||
writeToFileMissingContentError: () => {
|
||||
const instructions = getToolInstructionsReminder()
|
||||
|
||||
return `Missing value for required parameter 'content'. This most commonly happens when the file content is too large and your response was truncated before the 'content' parameter could be fully generated.
|
||||
|
||||
To recover, try one of these approaches:
|
||||
1. **Create a smaller file first**: Write a minimal version of the file with write_to_file, then use edit_file or apply_diff to add the remaining content incrementally.
|
||||
2. **Use edit_file instead**: If modifying an existing file, use edit_file with targeted changes rather than rewriting the entire file.
|
||||
3. **Split into multiple files**: If the content is genuinely large, consider splitting it across multiple smaller files.
|
||||
|
||||
Do NOT simply retry write_to_file with the same large content — it will likely fail again for the same reason.
|
||||
|
||||
${instructions}`
|
||||
},
|
||||
|
||||
invalidMcpToolArgumentError: (serverName: string, toolName: string) =>
|
||||
JSON.stringify({
|
||||
status: "error",
|
||||
|
|
|
|||
|
|
@ -42,7 +42,13 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> {
|
|||
if (newContent === undefined) {
|
||||
task.consecutiveMistakeCount++
|
||||
task.recordToolError("write_to_file")
|
||||
pushToolResult(await task.sayAndCreateMissingParamError("write_to_file", "content"))
|
||||
await task.say(
|
||||
"error",
|
||||
`Roo tried to use write_to_file${
|
||||
relPath ? ` for '${relPath.toPosix()}'` : ""
|
||||
} without value for required parameter 'content'. This is likely due to output token limits. Retrying...`,
|
||||
)
|
||||
pushToolResult(formatResponse.toolError(formatResponse.writeToFileMissingContentError()))
|
||||
await task.diffViewProvider.reset()
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ vi.mock("../../prompts/responses", () => ({
|
|||
toolError: vi.fn((msg) => `Error: ${msg}`),
|
||||
rooIgnoreError: vi.fn((path) => `Access denied: ${path}`),
|
||||
createPrettyPatch: vi.fn(() => "mock-diff"),
|
||||
writeToFileMissingContentError: vi.fn(
|
||||
() =>
|
||||
"Missing value for required parameter 'content'. This most commonly happens when the file content is too large and your response was truncated.",
|
||||
),
|
||||
},
|
||||
}))
|
||||
|
||||
|
|
@ -442,6 +446,63 @@ describe("writeToFileTool", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("missing parameter handling", () => {
|
||||
it("returns enhanced error when content is missing, suggesting alternatives to large file writes", async () => {
|
||||
// Directly build a toolUse with nativeArgs.content = undefined to simulate truncation
|
||||
const toolUse: ToolUse = {
|
||||
type: "tool_use",
|
||||
name: "write_to_file",
|
||||
params: { path: testFilePath },
|
||||
nativeArgs: { path: testFilePath, content: undefined } as any,
|
||||
partial: false,
|
||||
}
|
||||
|
||||
mockPushToolResult = vi.fn()
|
||||
|
||||
await writeToFileTool.handle(mockCline, toolUse as ToolUse<"write_to_file">, {
|
||||
askApproval: mockAskApproval,
|
||||
handleError: mockHandleError,
|
||||
pushToolResult: mockPushToolResult,
|
||||
})
|
||||
|
||||
expect(mockCline.consecutiveMistakeCount).toBe(1)
|
||||
expect(mockCline.recordToolError).toHaveBeenCalledWith("write_to_file")
|
||||
expect(mockCline.say).toHaveBeenCalledWith(
|
||||
"error",
|
||||
expect.stringContaining("without value for required parameter 'content'"),
|
||||
)
|
||||
expect(mockCline.say).toHaveBeenCalledWith("error", expect.stringContaining("output token limits"))
|
||||
expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("truncated"))
|
||||
expect(mockCline.diffViewProvider.reset).toHaveBeenCalled()
|
||||
// Should NOT call the generic sayAndCreateMissingParamError
|
||||
expect(mockCline.sayAndCreateMissingParamError).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("returns generic error when path is missing", async () => {
|
||||
// Directly build a toolUse with nativeArgs.path = undefined
|
||||
const toolUse: ToolUse = {
|
||||
type: "tool_use",
|
||||
name: "write_to_file",
|
||||
params: {},
|
||||
nativeArgs: { path: undefined, content: testContent } as any,
|
||||
partial: false,
|
||||
}
|
||||
|
||||
mockPushToolResult = vi.fn()
|
||||
|
||||
await writeToFileTool.handle(mockCline, toolUse as ToolUse<"write_to_file">, {
|
||||
askApproval: mockAskApproval,
|
||||
handleError: mockHandleError,
|
||||
pushToolResult: mockPushToolResult,
|
||||
})
|
||||
|
||||
expect(mockCline.consecutiveMistakeCount).toBe(1)
|
||||
expect(mockCline.recordToolError).toHaveBeenCalledWith("write_to_file")
|
||||
expect(mockCline.sayAndCreateMissingParamError).toHaveBeenCalledWith("write_to_file", "path")
|
||||
expect(mockCline.diffViewProvider.reset).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("error handling", () => {
|
||||
it("handles general file operation errors", async () => {
|
||||
mockCline.diffViewProvider.open.mockRejectedValue(new Error("General error"))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue