This commit is contained in:
Deleted user 2026-05-27 11:27:01 +08:00 committed by GitHub
commit ae199563ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 83 additions and 1 deletions

View file

@ -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",

View file

@ -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
}

View file

@ -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"))