Skip to content

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

Open
thomasxm wants to merge 2 commits intoopenclaw:mainfrom
thomasxm:fix/tool-path-tilde-expansion
Open

fix(agents): expand tilde in tool file paths#33501
thomasxm wants to merge 2 commits intoopenclaw:mainfrom
thomasxm:fix/tool-path-tilde-expansion

Conversation

@thomasxm
Copy link

@thomasxm thomasxm commented Mar 3, 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

This is a rebase of #30866 which was closed as a duplicate of #30431, but the two PRs have significantly different scope (see comparison below).

Why this is not a duplicate of #30431

Aspect #30431 This PR
Files changed 2 6
Path resolution points fixed 1 (normalizeToolParams only) 5 distinct call sites
writeHostFile (host write) Not fixed Fixed
readFile / access (host edit) Not fixed Fixed (already on main via earlier merge)
resolveWorkdir() (bash/exec cwd) Not fixed Fixed
parseSandboxBindMount() (Docker bind mounts) Not fixed Fixed
Workspace boundary (toRelativeWorkspacePath) Not fixed Covered (main now handles via expandPath)
Test cases 1 11
Windows CI fix No Yes (os.tmpdir() instead of hardcoded /tmp)
Closes issue #30335 #30669

#30431 only adds tilde expansion in normalizeToolParams, which handles the path parameter normalization for read/write/edit tool inputs. However, several other code paths resolve file paths independently of normalizeToolParams:

  1. writeHostFile() — the shared host write helper uses path.resolve() directly without tilde expansion
  2. resolveWorkdir() in bash-tools.shared.ts — the working directory for exec/bash tools bypasses normalizeToolParams entirely
  3. parseSandboxBindMount() in sandbox/fs-paths.ts — Docker bind mount host paths like ~/mydata:/data:rw are parsed separately

These three paths would remain broken with only #30431 merged.

Path resolution points covered

Tool path Status
writeHostFile() shared write helper Fixed
Host write mkdir non-workspace Fixed
Host edit readFile / access non-workspace Fixed
toRelativeWorkspacePath() workspace boundary Covered by main's expandPath()
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()

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)

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

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
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 3, 2026

Greptile Summary

This PR adds tilde (~) expansion before path.resolve() at all call sites that were missing it — specifically writeHostFile(), mkdir in write ops, readFile/access in edit ops, resolveWorkdir(), and parseSandboxBindMount(). The fix is consistent and correct: each call site now passes { home: os.homedir() } explicitly to expandHomePrefix, which intentionally bypasses OPENCLAW_HOME so ~ always expands to the real OS home. Additionally, the Windows CI regression in fs-safe.test.ts is fixed by replacing the hardcoded Unix path with os.tmpdir().

All identified path resolution points are now covered:

  • writeHostFile() shared write helper — Fixed with tilde expansion
  • Host write mkdir non-workspace — Fixed with tilde expansion
  • Host edit readFile/access non-workspace — Fixed with tilde expansion
  • toRelativeWorkspacePath() workspace boundary — Already covered by upstream expandPath()
  • resolveWorkdir() non-sandbox exec/bash workdir — Fixed with tilde expansion
  • parseSandboxBindMount() Docker bind mount host path — Fixed with tilde expansion

Test coverage is comprehensive (11 new tests) covering write, edit, workdir, and bind-mount path expansion, plus workspace boundary validation and non-tilde path handling.

Confidence Score: 5/5

  • This PR is safe to merge — it applies a narrow, well-tested fix to tilde path expansion across multiple call sites without altering broader logic.
  • All changed call sites are consistent and correct. The workspaceOnly=true code paths are already covered by expandPath() upstream. Test coverage is thorough (11 new tests covering write, edit, workdir, and bind-mount expansion, plus regression coverage for workspace boundary and non-tilde paths). The Windows CI fix is correct and localized. No logic regressions were identified.
  • No files require special attention.

Last reviewed commit: 861bc0a

@thomasxm thomasxm force-pushed the fix/tool-path-tilde-expansion branch from 861bc0a to 707d036 Compare March 3, 2026 21:24
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: 861bc0a01a

ℹ️ 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".

Comment on lines +163 to +166
const expanded = path.resolve(expandHomePrefix(workdir, { home: homedir() }));
const stats = statSync(expanded);
if (stats.isDirectory()) {
return workdir;
return expanded;

Choose a reason for hiding this comment

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

P1 Badge Preserve relative workdir for node-host exec

Converting workdir to an absolute path here changes semantics for exec host=node: resolveWorkdir now turns a relative input like . into a gateway-local absolute path, and that value is forwarded as cwd to node execution (src/agents/bash-tools.exec.ts:401-405). On the node side, approval hardening requires the provided cwd to exist locally (src/node-host/invoke-system-run-plan.ts:77-90), so remote nodes with different filesystem layouts can start failing with SYSTEM_RUN_DENIED where relative workdir previously worked.

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 in f8c9e28. resolveWorkdir now only returns the absolute path when tilde was actually expanded (expanded \!== workdir); otherwise it returns the original workdir value, preserving relative paths like . for node-host exec.

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: M labels Mar 3, 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: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant