mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
fix: detect and handle truncated write_to_file operations for large files
- Add detection for files >7000 lines when line_count is missing - Add validation to compare actual vs predicted line count with 10% tolerance - Provide clear error messages when truncation is detected - Add comprehensive tests for large file handling Fixes #6247
This commit is contained in:
parent
62160758fc
commit
9601be04ff
2 changed files with 131 additions and 1 deletions
|
|
@ -339,11 +339,82 @@ describe("writeToFileTool", () => {
|
|||
})
|
||||
|
||||
it("processes files with very large line counts", async () => {
|
||||
await executeWriteFileTool({ line_count: "999999" })
|
||||
// Create content that matches the line count to avoid mismatch error
|
||||
const largeContent = Array(999).fill("Line content").join("\n")
|
||||
await executeWriteFileTool({
|
||||
content: largeContent,
|
||||
line_count: "999",
|
||||
})
|
||||
|
||||
// Should process normally without issues
|
||||
expect(mockCline.consecutiveMistakeCount).toBe(0)
|
||||
})
|
||||
|
||||
it("detects potential truncation when line count is 0 and content exceeds 7000 lines", async () => {
|
||||
// Create content with more than 7000 lines
|
||||
const largeContent = Array(7500).fill("Line content").join("\n")
|
||||
|
||||
await executeWriteFileTool({
|
||||
content: largeContent,
|
||||
line_count: "0", // Missing line count
|
||||
})
|
||||
|
||||
expect(mockCline.consecutiveMistakeCount).toBe(1)
|
||||
expect(mockCline.recordToolError).toHaveBeenCalledWith("write_to_file")
|
||||
expect(mockCline.say).toHaveBeenCalledWith("error", expect.stringContaining("7500 lines"))
|
||||
expect(mockCline.diffViewProvider.reset).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("detects line count mismatch indicating truncation", async () => {
|
||||
// Create content with 7001 lines but claim it should have 10000
|
||||
const truncatedContent = Array(7001).fill("Line content").join("\n")
|
||||
|
||||
// Need to capture the tool result
|
||||
mockPushToolResult = vi.fn((result: ToolResponse) => {
|
||||
toolResult = result
|
||||
})
|
||||
|
||||
await executeWriteFileTool({
|
||||
content: truncatedContent,
|
||||
line_count: "10000", // Expected more lines
|
||||
})
|
||||
|
||||
expect(mockCline.consecutiveMistakeCount).toBe(1)
|
||||
expect(mockCline.recordToolError).toHaveBeenCalledWith("write_to_file")
|
||||
expect(mockCline.say).toHaveBeenCalledWith("error", expect.stringContaining("Line count mismatch"))
|
||||
// The error message should mention truncation since we have >7000 lines
|
||||
expect(toolResult).toContain("Content appears to be truncated")
|
||||
expect(toolResult).toContain("7001 lines")
|
||||
expect(mockCline.diffViewProvider.revertChanges).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("allows small line count differences within tolerance", async () => {
|
||||
// Create content with 95 lines when expecting 100 (5% difference)
|
||||
const content = Array(95).fill("Line content").join("\n")
|
||||
|
||||
await executeWriteFileTool({
|
||||
content: content,
|
||||
line_count: "100",
|
||||
})
|
||||
|
||||
// Should process normally as difference is within 10% tolerance
|
||||
expect(mockCline.consecutiveMistakeCount).toBe(0)
|
||||
expect(mockCline.diffViewProvider.saveChanges).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("handles exact line count match for large files", async () => {
|
||||
// Create content with exactly 8000 lines
|
||||
const largeContent = Array(8000).fill("Line content").join("\n")
|
||||
|
||||
await executeWriteFileTool({
|
||||
content: largeContent,
|
||||
line_count: "8000",
|
||||
})
|
||||
|
||||
// Should process normally
|
||||
expect(mockCline.consecutiveMistakeCount).toBe(0)
|
||||
expect(mockCline.diffViewProvider.saveChanges).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("partial block handling", () => {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,29 @@ export async function writeToFileTool(
|
|||
cline.diffViewProvider.editType = fileExists ? "modify" : "create"
|
||||
}
|
||||
|
||||
// Check for potential truncation in large files
|
||||
const actualLineCount = newContent.split("\n").length
|
||||
const LARGE_FILE_THRESHOLD = 7000
|
||||
|
||||
// If the file has many lines and no line_count was provided, it might be truncated
|
||||
if (actualLineCount > LARGE_FILE_THRESHOLD && predictedLineCount === 0) {
|
||||
cline.consecutiveMistakeCount++
|
||||
cline.recordToolError("write_to_file")
|
||||
|
||||
await cline.say(
|
||||
"error",
|
||||
`The file content appears to be very large (${actualLineCount} lines). The assistant may have reached its output limit and the content could be truncated. Please verify the file is complete or consider using a different approach for large files.`,
|
||||
)
|
||||
|
||||
pushToolResult(
|
||||
formatResponse.toolError(
|
||||
`Large file detected (${actualLineCount} lines). The content may be truncated due to output limits. Consider breaking the file into smaller chunks or using a different approach.`,
|
||||
),
|
||||
)
|
||||
await cline.diffViewProvider.reset()
|
||||
return
|
||||
}
|
||||
|
||||
// pre-processing newContent for cases where weaker models might add artifacts like markdown codeblock markers (deepseek/llama) or extra escape characters (gemini)
|
||||
if (newContent.startsWith("```")) {
|
||||
// cline handles cases where it includes language specifiers like ```python ```js
|
||||
|
|
@ -147,6 +170,42 @@ export async function writeToFileTool(
|
|||
return
|
||||
}
|
||||
|
||||
// Validate line count matches actual content
|
||||
const actualLineCount = newContent.split("\n").length
|
||||
const lineCountMismatchThreshold = 0.1 // 10% tolerance
|
||||
const lineCountDifference = Math.abs(actualLineCount - predictedLineCount)
|
||||
const lineCountDifferencePercent = lineCountDifference / predictedLineCount
|
||||
|
||||
// Check for significant mismatch that might indicate truncation
|
||||
if (predictedLineCount > 100 && lineCountDifferencePercent > lineCountMismatchThreshold) {
|
||||
cline.consecutiveMistakeCount++
|
||||
cline.recordToolError("write_to_file")
|
||||
|
||||
await cline.say(
|
||||
"error",
|
||||
`Line count mismatch detected. Expected ${predictedLineCount} lines but got ${actualLineCount} lines. This may indicate the content was truncated.`,
|
||||
)
|
||||
|
||||
// Check if this looks like truncation at output limit
|
||||
const TYPICAL_OUTPUT_LIMIT_LINES = 7000
|
||||
if (actualLineCount > TYPICAL_OUTPUT_LIMIT_LINES && actualLineCount < predictedLineCount) {
|
||||
pushToolResult(
|
||||
formatResponse.toolError(
|
||||
`Content appears to be truncated. The file should have ${predictedLineCount} lines but only ${actualLineCount} lines were provided. This often happens when files exceed ~7000 lines due to output token limits. Consider using apply_diff for large file modifications or breaking the content into smaller chunks.`,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
pushToolResult(
|
||||
formatResponse.toolError(
|
||||
`Line count mismatch: expected ${predictedLineCount} lines but got ${actualLineCount} lines. Please verify the content is complete.`,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
await cline.diffViewProvider.revertChanges()
|
||||
return
|
||||
}
|
||||
|
||||
cline.consecutiveMistakeCount = 0
|
||||
|
||||
// if isEditingFile false, that means we have the full contents of the file already.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue