Skip to content

Fix: read tool treats Windows absolute paths as relative to workspace (Issue #54039)#54205

Closed
yangcongcong-coding wants to merge 2 commits intoopenclaw:mainfrom
yangcongcong-coding:fix-windows-absolute-path-read
Closed

Fix: read tool treats Windows absolute paths as relative to workspace (Issue #54039)#54205
yangcongcong-coding wants to merge 2 commits intoopenclaw:mainfrom
yangcongcong-coding:fix-windows-absolute-path-read

Conversation

@yangcongcong-coding
Copy link
Copy Markdown

@yangcongcong-coding yangcongcong-coding commented Mar 25, 2026

Fixes #54039.

Problem

The read tool incorrectly treats Windows absolute paths as relative to the workspace root due to a bug in the upstream @mariozechner/pi-coding-agent dependency. When an LLM requests a Windows path, pi-coding-agent blindly uses path.join to 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 read tool arguments, it uses path.relative to convert it into a true relative path against the active workspaceRoot. When this path is passed down to the upstream dependency, its internal path.join(workspaceRoot, inputPath) mathematically correctly restores the exact absolute path intended by the LLM, fixing both intra-workspace and cross-workspace absolute paths cleanly.

  • I have read the contribution guidelines
  • * [x] My code follows the code style of this project
  • * [x] All new and existing tests pass

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: XS labels Mar 25, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 25, 2026

Greptile Summary

This PR fixes a bug where OpenClaw's read tool incorrectly doubled Windows absolute paths by adding an absolute→relative path conversion in the wrapper layer before the path reaches the upstream @mariozechner/pi-coding-agent dependency's path.join(workspaceRoot, inputPath) call.

Key changes:

  • OpenClawReadToolOptions gains an optional workspaceRoot field.
  • createOpenClawReadTool now detects absolute paths in args.path and converts them to paths relative to workspaceRoot via path.relative, so that the downstream path.join mathematically restores the intended absolute path.
  • createOpenClawCodingTools passes the existing workspaceRoot into the new option.

Issue found:

  • The fix works correctly for same-drive absolute paths (the primary reported scenario) but falls short for cross-drive Windows paths (e.g. workspace on C: but requested path on D:). In that case path.relative returns the absolute path unchanged, and the subsequent path.join produces an incorrect doubled result. Adding a guard that skips the conversion when path.relative itself returns an absolute result would close this gap.

Confidence Score: 4/5

  • Safe to merge for the primary bug scenario; a minor cross-drive edge case remains but is rare in practice.
  • The fix is well-scoped and correctly resolves the reported bug (same-drive Windows absolute paths being doubled). The cross-drive path limitation is a real gap but represents a narrow edge case and does not regress existing behavior. One targeted follow-up can close it.
  • src/agents/pi-tools.read.ts — the cross-drive guard should be added before merging if cross-drive support is expected.
Prompt To Fix All 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.

Reviews (1): Last reviewed commit: "CLI: fix read tool treating Windows abso..." | Re-trigger Greptile

Comment thread src/agents/pi-tools.read.ts Outdated
Comment on lines +652 to +656
if (options?.workspaceRoot && typeof safeArgs.path === "string" && path.isAbsolute(safeArgs.path)) {
safeArgs = {
...safeArgs,
path: path.relative(options.workspaceRoot, safeArgs.path) || ".",
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

@steipete
Copy link
Copy Markdown
Contributor

Closing this as implemented after Codex review.

Current main already contains the Windows drive-path handling for sandbox/read-path resolution that makes this PR redundant.

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.

@steipete steipete closed this Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: read tool prepends workspace root to absolute Windows paths, producing doubled paths

2 participants