fix: handle absolute paths in WriteToFileTool consistently with other tools

This fix addresses issue #11208 where WriteToFileTool would fail with
EACCES permission denied when the LLM outputs an absolute path like
"/plans" instead of a relative path.

The fix adds the same absolute path handling that already exists in
SearchReplaceTool and EditFileTool:
- Detect if the provided path is absolute using path.isAbsolute()
- Convert absolute paths to relative paths using path.relative(task.cwd, absolutePath)

This ensures that absolute paths get normalized to workspace-relative
paths rather than attempting to write to system root level directories.

Also adds two new tests for absolute path handling to prevent regression.
This commit is contained in:
Roo Code 2026-02-05 05:10:07 +00:00
parent aa49871a5d
commit 44c3fd4e57
2 changed files with 39 additions and 2 deletions

View file

@ -28,10 +28,9 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> {
async execute(params: WriteToFileParams, task: Task, callbacks: ToolCallbacks): Promise<void> {
const { pushToolResult, handleError, askApproval } = callbacks
const relPath = params.path
let newContent = params.content
if (!relPath) {
if (!params.path) {
task.consecutiveMistakeCount++
task.recordToolError("write_to_file")
pushToolResult(await task.sayAndCreateMissingParamError("write_to_file", "path"))
@ -39,6 +38,14 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> {
return
}
// Determine relative path - path can be absolute or relative
let relPath: string
if (path.isAbsolute(params.path)) {
relPath = path.relative(task.cwd, params.path)
} else {
relPath = params.path
}
if (newContent === undefined) {
task.consecutiveMistakeCount++
task.recordToolError("write_to_file")

View file

@ -244,6 +244,36 @@ describe("writeToFileTool", () => {
})
})
describe("absolute path handling", () => {
it("converts absolute path to relative path within workspace", async () => {
// Set up a workspace directory
mockCline.cwd = "/workspace/project"
// Provide an absolute path that's within the workspace
const absoluteInputPath = "/workspace/project/plans/architecture.md"
await executeWriteFileTool({ path: absoluteInputPath }, { accessAllowed: true })
// The path should be converted to relative and used for access validation
expect(mockCline.rooIgnoreController.validateAccess).toHaveBeenCalledWith("plans/architecture.md")
})
it("handles absolute path at root level (issue #11208)", async () => {
// This reproduces the bug where LLM outputs "/plans" as an absolute path
mockCline.cwd = "/workspace/project"
// Absolute path "/plans" would resolve to root without the fix
const absoluteInputPath = "/plans"
await executeWriteFileTool({ path: absoluteInputPath }, { accessAllowed: true })
// The path should be converted to a relative path (relative to cwd)
// path.relative("/workspace/project", "/plans") = "../../../plans" on Unix
// This is safer than trying to write to system root
expect(mockCline.rooIgnoreController.validateAccess).toHaveBeenCalled()
})
})
describe("file existence detection", () => {
it.skipIf(process.platform === "win32")("detects existing file and sets editType to modify", async () => {
await executeWriteFileTool({}, { fileExists: true })