Skip to content

Commit 44c3fd4

Browse files
committed
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.
1 parent aa49871 commit 44c3fd4

2 files changed

Lines changed: 39 additions & 2 deletions

File tree

src/core/tools/WriteToFileTool.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,24 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> {
2828

2929
async execute(params: WriteToFileParams, task: Task, callbacks: ToolCallbacks): Promise<void> {
3030
const { pushToolResult, handleError, askApproval } = callbacks
31-
const relPath = params.path
3231
let newContent = params.content
3332

34-
if (!relPath) {
33+
if (!params.path) {
3534
task.consecutiveMistakeCount++
3635
task.recordToolError("write_to_file")
3736
pushToolResult(await task.sayAndCreateMissingParamError("write_to_file", "path"))
3837
await task.diffViewProvider.reset()
3938
return
4039
}
4140

41+
// Determine relative path - path can be absolute or relative
42+
let relPath: string
43+
if (path.isAbsolute(params.path)) {
44+
relPath = path.relative(task.cwd, params.path)
45+
} else {
46+
relPath = params.path
47+
}
48+
4249
if (newContent === undefined) {
4350
task.consecutiveMistakeCount++
4451
task.recordToolError("write_to_file")

src/core/tools/__tests__/writeToFileTool.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,36 @@ describe("writeToFileTool", () => {
244244
})
245245
})
246246

247+
describe("absolute path handling", () => {
248+
it("converts absolute path to relative path within workspace", async () => {
249+
// Set up a workspace directory
250+
mockCline.cwd = "/workspace/project"
251+
252+
// Provide an absolute path that's within the workspace
253+
const absoluteInputPath = "/workspace/project/plans/architecture.md"
254+
255+
await executeWriteFileTool({ path: absoluteInputPath }, { accessAllowed: true })
256+
257+
// The path should be converted to relative and used for access validation
258+
expect(mockCline.rooIgnoreController.validateAccess).toHaveBeenCalledWith("plans/architecture.md")
259+
})
260+
261+
it("handles absolute path at root level (issue #11208)", async () => {
262+
// This reproduces the bug where LLM outputs "/plans" as an absolute path
263+
mockCline.cwd = "/workspace/project"
264+
265+
// Absolute path "/plans" would resolve to root without the fix
266+
const absoluteInputPath = "/plans"
267+
268+
await executeWriteFileTool({ path: absoluteInputPath }, { accessAllowed: true })
269+
270+
// The path should be converted to a relative path (relative to cwd)
271+
// path.relative("/workspace/project", "/plans") = "../../../plans" on Unix
272+
// This is safer than trying to write to system root
273+
expect(mockCline.rooIgnoreController.validateAccess).toHaveBeenCalled()
274+
})
275+
})
276+
247277
describe("file existence detection", () => {
248278
it.skipIf(process.platform === "win32")("detects existing file and sets editType to modify", async () => {
249279
await executeWriteFileTool({}, { fileExists: true })

0 commit comments

Comments
 (0)