Skip to content

fix(agents): expand tilde in tool file paths#30866

Closed
thomasxm wants to merge 1 commit intoopenclaw:mainfrom
thomasxm:fix/tool-path-tilde-expansion
Closed

fix(agents): expand tilde in tool file paths#30866
thomasxm wants to merge 1 commit intoopenclaw:mainfrom
thomasxm:fix/tool-path-tilde-expansion

Conversation

@thomasxm
Copy link

@thomasxm thomasxm commented Mar 1, 2026

Summary

  • path.resolve("~/file.txt") treats ~ as a literal directory name, resolving to <cwd>/~/file.txt instead of /home/user/file.txt
  • Add expandHomePrefix(..., { home: os.homedir() }) before path.resolve() at all affected tool path resolution points, ensuring ~ always expands to the real OS home (not OPENCLAW_HOME)
  • Fix a pre-existing Windows CI failure in fs-safe.test.ts where a hardcoded Unix path /tmp/fake-home-test broke on Windows

Path resolution points covered

Tool path Status
Host write (mkdir, writeFile) non-workspace Fixed
Host edit (readFile, writeFile, access) non-workspace Fixed
toRelativePathInRoot() workspace boundary (all tools) Fixed
resolveWorkdir() non-sandbox exec/bash workdir Fixed
parseSandboxBindMount() Docker bind mount host path Fixed
Host read tool Already handled by upstream expandPath()
Sandbox read/write/edit Already handled by upstream expandPath()
Sandbox workdir Already handled via assertSandboxPath()

Files changed

Production (4):

  • src/agents/pi-tools.read.ts — 1 import + 6 call sites
  • src/agents/bash-tools.shared.ts — 1 import, tilde expansion + path normalization in resolveWorkdir()
  • src/agents/sandbox/fs-paths.ts — 1 import + 1 call site in parseSandboxBindMount()
  • src/infra/fs-safe.test.ts — fix hardcoded Unix path for Windows CI

Test (2):

  • src/agents/pi-tools.read.tilde-expansion.test.ts — 10 tests (new file)
  • src/agents/sandbox/fs-paths.test.ts — 1 new test

Test plan

  • Host-wide write with ~ path creates file in home directory
  • Host-wide write mkdir with ~ creates directory in home
  • Host-wide edit readFile with ~ reads from home
  • Host-wide edit access with ~ checks home path
  • Workspace-mode rejects ~ paths outside workspace root
  • Workspace-mode accepts ~ paths inside workspace root
  • Absolute paths without tilde are unaffected
  • resolveWorkdir("~") returns home directory
  • resolveWorkdir("~/subdir") returns home subdirectory
  • resolveWorkdir falls back with warning for nonexistent ~ subdir
  • parseSandboxBindMount("~/mydata:/data:rw") expands host path
  • expandHomePrefix test uses platform-correct paths (Windows CI fix)
  • 172+ related tests pass, zero type errors

Closes #30669
Related: #30782, #30788, #30744, #30770, #30756, #30753, #30752, #30747

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: M labels Mar 1, 2026
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe73537d2d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

export function resolveWorkdir(workdir: string, warnings: string[]) {
const current = safeCwd();
const fallback = current ?? homedir();
const expanded = path.resolve(expandHomePrefix(workdir, { home: homedir() }));

Choose a reason for hiding this comment

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

P2 Badge Handle missing cwd before resolving relative workdir

If the process current directory has been deleted/unmounted, safeCwd() correctly falls back, but path.resolve(...) now runs before the try block and throws ENOENT for relative workdir values, so this function exits with an uncaught error instead of emitting the warning and using the fallback. This regresses resolveWorkdir in the exact scenario it was already designed to tolerate, and it can break tools.exec requests that pass relative paths after cwd loss.

Useful? React with 👍 / 👎.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed — moved path.resolve(expandHomePrefix(...)) inside the try block so ENOENT from a deleted cwd is caught by the existing catch handler.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 1, 2026

Greptile Summary

This PR correctly fixes tilde (~) expansion in file paths across the agents codebase. Previously, path.resolve("~/file.txt") treated ~ as a literal directory name. The fix adds expandHomePrefix(..., { home: os.homedir() }) before path.resolve() at all necessary locations.

Key changes:

  • Host write/edit operations (pi-tools.read.ts): Added tilde expansion to 6 call sites (mkdir, writeFile, readFile, access, and workspace boundary check)
  • Bash workdir resolution (bash-tools.shared.ts): Enabled resolveWorkdir("~") to correctly return the home directory
  • Docker bind mounts (sandbox/fs-paths.ts): Host paths in bind specs like ~/mydata:/data:rw now expand correctly
  • Windows compatibility: Fixed hardcoded Unix path in test that broke Windows CI

Implementation quality:

  • Consistent pattern across all changes
  • Comprehensive test coverage (10 new tests covering all code paths)
  • Handles both Unix (~/) and Windows (~\) path separators via regex /^~(?=$|[\\/])/
  • Workspace boundary checks correctly applied after tilde expansion
  • Explicitly uses os.homedir() instead of OPENCLAW_HOME as intended

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation is correct, well-tested, and follows a consistent pattern. All path resolution points are properly covered, workspace security boundaries work correctly with expanded paths, and comprehensive tests verify the behavior across different scenarios including edge cases. The Windows CI fix addresses a real platform compatibility issue. No logical errors or security concerns identified.
  • No files require special attention

Last reviewed commit: fe73537

path.resolve() treats ~ as a literal directory name, so ~/file.txt
resolved to <cwd>/~/file.txt instead of /home/user/file.txt. Add
expandHomePrefix() before path.resolve() in all affected tool path
resolution points:

- Host write operations (mkdir, writeFile) in non-workspace mode
- Host edit operations (readFile, writeFile, access) in non-workspace mode
- toRelativePathInRoot() workspace boundary validator (affects all tools)
- resolveWorkdir() for non-sandbox exec/bash working directory
- parseSandboxBindMount() for Docker bind mount host paths

The host read tool and sandbox tools already handled tilde via the
upstream library's expandPath().

Closes openclaw#30669
Related: openclaw#30782, openclaw#30788, openclaw#30744, openclaw#30770, openclaw#30756, openclaw#30753, openclaw#30752, openclaw#30747
@thomasxm thomasxm force-pushed the fix/tool-path-tilde-expansion branch from fe73537 to 991d782 Compare March 1, 2026 18:24
@steipete
Copy link
Contributor

steipete commented Mar 2, 2026

Thanks for the PR! Multiple PRs address the same fix. Keeping #30431 as the earliest submission. Closing to reduce noise. This is an AI-assisted triage review. If we got this wrong, feel free to reopen — happy to revisit.

@thomasxm
Copy link
Author

thomasxm commented Mar 3, 2026

@steipete Thanks for the note — reopening as #33501 (force-push during rebase prevented reopening this one).

This PR is not a duplicate of #30431. The two PRs have significantly different scope:

#30431 touches 2 files and fixes tilde expansion in one location: normalizeToolParams(). This handles the path parameter for read/write/edit tool inputs.

This PR (#30866, now #33501) touches 6 files and fixes 5 distinct path resolution points, three of which are completely independent of normalizeToolParams and would remain broken with only #30431 merged:

  1. writeHostFile() — the shared host write helper calls path.resolve() directly without tilde expansion. normalizeToolParams doesn't help here because writeHostFile receives the path after it's already been resolved through a different code path.

  2. resolveWorkdir() in bash-tools.shared.ts — the working directory for exec/bash tools bypasses normalizeToolParams entirely. Running cd ~/projects would resolve to <cwd>/~/projects instead of /home/user/projects.

  3. parseSandboxBindMount() in sandbox/fs-paths.ts — Docker bind mount specs like ~/mydata:/data:rw are parsed in a completely separate code path. The host path portion would remain a literal ~/mydata.

Additionally, this PR includes 11 test cases vs 1 in #30431, and fixes a pre-existing Windows CI failure where a hardcoded /tmp/fake-home-test path broke on Windows (replaced with os.tmpdir()).

See the detailed comparison table in #33501.

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: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Edit tool does not expand ~ (tilde) in file paths

2 participants