Skip to content

fix(workspace): preserve remote POSIX paths without macOS synthetic resolution - #7132

Merged
nesquena-hermes merged 3 commits into
nesquena:masterfrom
alfred-rootson:fix/remote-linux-workspace-macos-resolution
Aug 19, 2026
Merged

nesquena-hermes merged 3 commits into
nesquena:masterfrom
alfred-rootson:fix/remote-linux-workspace-macos-resolution

Conversation

@alfred-rootson

Copy link
Copy Markdown
Contributor

Fixes #7131

Problem

When Hermes WebUI runs on macOS and uses a remote terminal profile (e.g., SSH backend) pointing to a Linux machine with terminal.cwd set to /home/<user>, workspace path resolution fails with:

Error: Path does not exist: /System/Volumes/Data/home/<user>

On macOS (Darwin), /home is a synthetic firmlink mapped to /System/Volumes/Data/home. Calling Python's _resolve_path() / Path.resolve() on a target-side Linux path causes macOS to canonicalize /home into /System/Volumes/Data/home. Subsequent local filesystem checks (Path.exists()) fail because that path does not exist on the local macOS host.

Solution

  1. Return Path(normalized_raw) directly from _remote_terminal_workspace_candidate() once validated, bypassing local macOS _resolve_path().
  2. In _clean_workspace_list(), check _remote_terminal_workspace_candidate(path) before attempting local _safe_resolve().
  3. Add a unit test in tests/test_remote_terminal_workspace.py ensuring Linux home paths like /home/developer do not expand to /System/Volumes/Data/home/developer on macOS.

Verification

Ran pytest tests/test_remote_terminal_workspace.py — 16/16 passed.

@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR preserves validated target-side POSIX workspace paths instead of canonicalizing them through the WebUI host.

  • Returns normalized remote POSIX paths without macOS synthetic-path rewriting.
  • Preserves remote paths while cleaning saved workspace entries.
  • Adds regression coverage for remote Linux home paths on macOS.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
api/workspace.py Remote workspace candidates and saved workspace entries now preserve normalized target-side POSIX paths without host-local canonicalization.
tests/test_remote_terminal_workspace.py Adds regression coverage confirming that /home/developer remains unchanged when macOS-style synthetic resolution is simulated.

Reviews (6): Last reviewed commit: "ci: trigger required checks (workflow di..." | Re-trigger Greptile

Comment thread api/workspace.py
@nesquena-hermes nesquena-hermes added the size:S Small PR (≤2 files, ≤30 LOC) label Aug 18, 2026
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Summary

I read the complete two-file patch at head e2ad0df2, the full workspace module at the PR head and on origin/master, and the complete remote-workspace test file in both revisions. The POSIX fix is in the correct layer: once target-side containment has been checked with PurePosixPath, returning the normalized target path avoids host macOS firmlink resolution. The new test does not actually reproduce that distinction on the Linux CI path, however, and the patch also changes the non-POSIX fallback without coverage.

Code reference

The intended fix is at api/workspace.py:178-187, and saved-workspace cleanup now consults target-side semantics first at api/workspace.py:247-268:

if normalized_raw is not None and normalized_cwd is not None:
    ...
    if posix_candidate == posix_base or _posix_is_within(posix_candidate, posix_base):
        return Path(normalized_raw)
...
if candidate == base or _is_within(candidate, base):
    return Path(raw)

The first return is exactly what the reported macOS case needs. validate_workspace_to_add() and resolve_trusted_workspace() still perform the host-local probe first at api/workspace.py:951-978 and :809-846, but both correctly prefer remote_candidate when the local probe fails.

The regression at tests/test_remote_terminal_workspace.py:150-159 uses the target Linux home path directly. On Linux, current master resolves that path to itself, so the assertions can pass before the patch. It does not force the pre-fix macOS transformation to the synthetic target. It also does not cover the second changed return, where relative or Windows-shaped remote paths now return Path(raw) instead of the previously resolved candidate.

Diagnosis / recommendation

Keep the absolute-POSIX branch change. Make the regression non-vacuous by monkeypatching _resolve_path so the target Linux home path becomes the synthetic macOS path, while _remote_terminal_workspace_candidate() must still return the original normalized POSIX path. Exercise validate_workspace_to_add, resolve_trusted_workspace, and _clean_workspace_list under that simulated host behavior.

Unless preserving raw non-POSIX target paths is independently required, leave the fallback return as candidate to keep this PR scoped to the reported POSIX/macOS defect. If the fallback change is intentional, add cases for a relative remote cwd and a Windows-style remote cwd, including saved-workspace cleanup.

Verification step

The new test should fail on origin/master because the simulated host resolver rewrites the target, then pass only when the POSIX branch bypasses host resolution. I did not execute PR-authored code during this read-only review.

@alfred-rootson
alfred-rootson force-pushed the fix/remote-linux-workspace-macos-resolution branch from e2ad0df to 7b72267 Compare August 19, 2026 11:25
@alfred-rootson

Copy link
Copy Markdown
Contributor Author

Thank you for the thorough review and insightful suggestions!

I have updated the PR (commit 7b72267d) with both recommendations:

  1. Strict Scoping: Reverted the non-POSIX / relative fallback branch in _remote_terminal_workspace_candidate() back to return candidate so this PR remains strictly focused on the POSIX/macOS synthetic resolution defect.
  2. Robust Cross-Platform Regression Test: Updated test_remote_terminal_linux_home_preserves_path_without_macos_synthetic_resolution in tests/test_remote_terminal_workspace.py to simulate macOS firmlink resolution via monkeypatch.setattr(workspace, "_resolve_path", fake_resolve).
    • Verified that on unpatched origin/master, this test actively fails across all platforms (Linux/macOS/Windows) with:
      AssertionError: assert PosixPath('/System/Volumes/Data/home/developer') == PosixPath('/home/developer')
    • With this PR applied, all 16 tests in test_remote_terminal_workspace.py pass cleanly.

Ready for re-review.

…esolution (nesquena#7131)

When hermes-webui runs on macOS with a remote terminal profile pointing to
a Linux host (e.g. terminal.cwd='/home/<user>'), path resolution previously
invoked Path.resolve(), expanding '/home' via macOS synthetic firmlinks to
'/System/Volumes/Data/home/<user>' and failing validation.

- Return Path(normalized_raw) directly from _remote_terminal_workspace_candidate
- Check _remote_terminal_workspace_candidate in _clean_workspace_list before local _safe_resolve
- Add unit test covering Linux /home/<user> remote paths without macOS expansion
@alfred-rootson
alfred-rootson force-pushed the fix/remote-linux-workspace-macos-resolution branch from b096714 to 6199ba3 Compare August 19, 2026 12:01
@nesquena-hermes
nesquena-hermes enabled auto-merge (squash) August 19, 2026 13:31
@nesquena-hermes
nesquena-hermes merged commit 7b38e9a into nesquena:master Aug 19, 2026
23 checks passed
nesquena-hermes added a commit that referenced this pull request Aug 19, 2026
…alfred-rootson) (#7150)

Co-authored-by: n <a@n>
Co-authored-by: alfred-rootson <alfred-rootson@users.noreply.github.com>
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Shipping in exp-v0.52.249 — thanks @alfred-rootson. Verified the containment boundary holds: POSIX normalization rejects .. traversal, NUL bytes, backslash tricks, and blocked system roots before the lexical within-terminal.cwd check, remote detection keys off terminal.backend (not the requested path, so it's not spoofable), and local workspace resolution/containment is unchanged. Nice host-agnostic fix — it also helps a Linux host with a remote profile, not just macOS.

alai04 pushed a commit to alai04/hermes-webui that referenced this pull request Aug 31, 2026
…esolution (nesquena#7132)

* fix(workspace): preserve remote POSIX paths without macOS synthetic resolution (nesquena#7131)

When hermes-webui runs on macOS with a remote terminal profile pointing to
a Linux host (e.g. terminal.cwd='/home/<user>'), path resolution previously
invoked Path.resolve(), expanding '/home' via macOS synthetic firmlinks to
'/System/Volumes/Data/home/<user>' and failing validation.

- Return Path(normalized_raw) directly from _remote_terminal_workspace_candidate
- Check _remote_terminal_workspace_candidate in _clean_workspace_list before local _safe_resolve
- Add unit test covering Linux /home/<user> remote paths without macOS expansion

* ci: trigger required checks (workflow did not run on fork head)

---------

Co-authored-by: n <a@n>
alai04 pushed a commit to alai04/hermes-webui that referenced this pull request Aug 31, 2026
…a#7132, @alfred-rootson) (nesquena#7150)

Co-authored-by: n <a@n>
Co-authored-by: alfred-rootson <alfred-rootson@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S Small PR (≤2 files, ≤30 LOC)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remote terminal workspace fails on macOS when remote cwd is a Linux path (/home/<user>)

2 participants