Fix: read tool treats Windows absolute paths as relative to workspace (Issue #54039)#54205
Conversation
Greptile SummaryThis PR fixes a bug where OpenClaw's Key changes:
Issue found:
Confidence Score: 4/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/agents/pi-tools.read.ts
Line: 652-656
Comment:
**Cross-drive Windows paths still broken**
On Windows, when `workspaceRoot` and the absolute path reside on **different drives** (e.g. workspace is `C:\projects\foo` but the LLM requests `D:\data\file.txt`), `path.relative` cannot express the relationship and returns the original absolute path unchanged:
```
path.relative('C:\\projects\\foo', 'D:\\data\\file.txt')
// => 'D:\\data\\file.txt' (unchanged, different drives)
```
That unchanged value is then passed through `path.join('C:\\projects\\foo', 'D:\\data\\file.txt')`, which on Windows treats the drive-letter segment as a literal filename component, producing `C:\projects\foo\D:\data\file.txt` — still wrong.
The fix works correctly for the common case (same-drive absolute paths), but the PR description's claim of fixing "both intra-workspace and cross-workspace absolute paths cleanly" is a bit overstated for this cross-drive edge case. It may be worth adding a comment in the code noting this known limitation, or adding an early-exit guard:
```typescript
if (options?.workspaceRoot && typeof safeArgs.path === "string" && path.isAbsolute(safeArgs.path)) {
const rel = path.relative(options.workspaceRoot, safeArgs.path);
// path.relative returns the original absolute path when drives differ on Windows;
// in that case, keep the path as-is rather than producing a malformed joined path.
if (!path.isAbsolute(rel)) {
safeArgs = { ...safeArgs, path: rel || "." };
}
}
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "CLI: fix read tool treating Windows abso..." | Re-trigger Greptile |
| if (options?.workspaceRoot && typeof safeArgs.path === "string" && path.isAbsolute(safeArgs.path)) { | ||
| safeArgs = { | ||
| ...safeArgs, | ||
| path: path.relative(options.workspaceRoot, safeArgs.path) || ".", | ||
| }; |
There was a problem hiding this comment.
Cross-drive Windows paths still broken
On Windows, when workspaceRoot and the absolute path reside on different drives (e.g. workspace is C:\projects\foo but the LLM requests D:\data\file.txt), path.relative cannot express the relationship and returns the original absolute path unchanged:
path.relative('C:\\projects\\foo', 'D:\\data\\file.txt')
// => 'D:\\data\\file.txt' (unchanged, different drives)
That unchanged value is then passed through path.join('C:\\projects\\foo', 'D:\\data\\file.txt'), which on Windows treats the drive-letter segment as a literal filename component, producing C:\projects\foo\D:\data\file.txt — still wrong.
The fix works correctly for the common case (same-drive absolute paths), but the PR description's claim of fixing "both intra-workspace and cross-workspace absolute paths cleanly" is a bit overstated for this cross-drive edge case. It may be worth adding a comment in the code noting this known limitation, or adding an early-exit guard:
if (options?.workspaceRoot && typeof safeArgs.path === "string" && path.isAbsolute(safeArgs.path)) {
const rel = path.relative(options.workspaceRoot, safeArgs.path);
// path.relative returns the original absolute path when drives differ on Windows;
// in that case, keep the path as-is rather than producing a malformed joined path.
if (!path.isAbsolute(rel)) {
safeArgs = { ...safeArgs, path: rel || "." };
}
}Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-tools.read.ts
Line: 652-656
Comment:
**Cross-drive Windows paths still broken**
On Windows, when `workspaceRoot` and the absolute path reside on **different drives** (e.g. workspace is `C:\projects\foo` but the LLM requests `D:\data\file.txt`), `path.relative` cannot express the relationship and returns the original absolute path unchanged:
```
path.relative('C:\\projects\\foo', 'D:\\data\\file.txt')
// => 'D:\\data\\file.txt' (unchanged, different drives)
```
That unchanged value is then passed through `path.join('C:\\projects\\foo', 'D:\\data\\file.txt')`, which on Windows treats the drive-letter segment as a literal filename component, producing `C:\projects\foo\D:\data\file.txt` — still wrong.
The fix works correctly for the common case (same-drive absolute paths), but the PR description's claim of fixing "both intra-workspace and cross-workspace absolute paths cleanly" is a bit overstated for this cross-drive edge case. It may be worth adding a comment in the code noting this known limitation, or adding an early-exit guard:
```typescript
if (options?.workspaceRoot && typeof safeArgs.path === "string" && path.isAbsolute(safeArgs.path)) {
const rel = path.relative(options.workspaceRoot, safeArgs.path);
// path.relative returns the original absolute path when drives differ on Windows;
// in that case, keep the path as-is rather than producing a malformed joined path.
if (!path.isAbsolute(rel)) {
safeArgs = { ...safeArgs, path: rel || "." };
}
}
```
How can I resolve this? If you propose a fix, please make it concise.|
Closing this as implemented after Codex review. Current What I checked:
So I’m closing this as already implemented rather than keeping a duplicate issue open. Review notes: reviewed against 8f64cd3e4d83; fix evidence: commit 0a3da5cd8a6e. |
Fixes #54039.
Problem
The
readtool incorrectly treats Windows absolute paths as relative to the workspace root due to a bug in the upstream@mariozechner/pi-coding-agentdependency. When an LLM requests a Windows path,pi-coding-agentblindly usespath.jointo append it to the workspace root, creating an invalid doubled path.Solution
Since the external package shouldn't be patched in this repo, this PR implements a workaround in OpenClaw's tool wrapper layer (
src/agents/pi-tools.read.ts).When the OpenClaw wrapper detects an absolute path in the
readtool arguments, it usespath.relativeto convert it into a true relative path against the activeworkspaceRoot. When this path is passed down to the upstream dependency, its internalpath.join(workspaceRoot, inputPath)mathematically correctly restores the exact absolute path intended by the LLM, fixing both intra-workspace and cross-workspace absolute paths cleanly.