fix(file-tools): reject sentinel TERMINAL_CWD; anchor worktree edits before live cwd exists#41861
Merged
Merged
Conversation
Contributor
🔎 Lint report:
|
220b625 to
55a9245
Compare
…before live cwd exists Completes the worktree-misroute fix from #35399, which made misroutes visible (resolved_path) but did not prevent them: its divergence warning only fired once a terminal command had populated the live cwd registry. A fresh worktree session (registry still empty) with a stale TERMINAL_CWD='.' got neither a worktree anchor nor a warning, so a relative write_file/patch silently landed in the MAIN checkout. Two changes in tools/file_tools.py: - Treat sentinel TERMINAL_CWD values ('', '.', './', 'auto', 'cwd') and any relative value as UNSET rather than a literal anchor. Previously '.' was joined onto the process cwd, silently routing edits to wherever the process happened to be (the main repo, in a worktree session). The gateway already sanitizes the same set at import time; the file-tool layer now matches. - New _authoritative_workspace_root(): prefers the live terminal cwd, else a sentinel-free absolute TERMINAL_CWD (the worktree path cli.py/main.py set for -w). _resolve_base_dir() and _path_resolution_warning() both use it, so a worktree session resolves into — and warns about escaping — the worktree from the very first write, before any cd has run. Validation: 11 new/parametrized tests (sentinel handling, empty-registry anchoring, early divergence warning, live-cwd precedence). 32/32 pass under scripts/run_tests.sh. Live E2E: relative write in an empty-registry worktree session lands in the worktree, main untouched.
55a9245 to
2106717
Compare
a249169329-cpu
pushed a commit
to a249169329-cpu/hermes-agent
that referenced
this pull request
Jun 8, 2026
…before live cwd exists (NousResearch#41861) Completes the worktree-misroute fix from NousResearch#35399, which made misroutes visible (resolved_path) but did not prevent them: its divergence warning only fired once a terminal command had populated the live cwd registry. A fresh worktree session (registry still empty) with a stale TERMINAL_CWD='.' got neither a worktree anchor nor a warning, so a relative write_file/patch silently landed in the MAIN checkout. Two changes in tools/file_tools.py: - Treat sentinel TERMINAL_CWD values ('', '.', './', 'auto', 'cwd') and any relative value as UNSET rather than a literal anchor. Previously '.' was joined onto the process cwd, silently routing edits to wherever the process happened to be (the main repo, in a worktree session). The gateway already sanitizes the same set at import time; the file-tool layer now matches. - New _authoritative_workspace_root(): prefers the live terminal cwd, else a sentinel-free absolute TERMINAL_CWD (the worktree path cli.py/main.py set for -w). _resolve_base_dir() and _path_resolution_warning() both use it, so a worktree session resolves into — and warns about escaping — the worktree from the very first write, before any cd has run. Validation: 11 new/parametrized tests (sentinel handling, empty-registry anchoring, early divergence warning, live-cwd precedence). 32/32 pass under scripts/run_tests.sh. Live E2E: relative write in an empty-registry worktree session lands in the worktree, main untouched.
changman
pushed a commit
to changman/hermes-agent
that referenced
this pull request
Jun 10, 2026
…before live cwd exists (NousResearch#41861) Completes the worktree-misroute fix from NousResearch#35399, which made misroutes visible (resolved_path) but did not prevent them: its divergence warning only fired once a terminal command had populated the live cwd registry. A fresh worktree session (registry still empty) with a stale TERMINAL_CWD='.' got neither a worktree anchor nor a warning, so a relative write_file/patch silently landed in the MAIN checkout. Two changes in tools/file_tools.py: - Treat sentinel TERMINAL_CWD values ('', '.', './', 'auto', 'cwd') and any relative value as UNSET rather than a literal anchor. Previously '.' was joined onto the process cwd, silently routing edits to wherever the process happened to be (the main repo, in a worktree session). The gateway already sanitizes the same set at import time; the file-tool layer now matches. - New _authoritative_workspace_root(): prefers the live terminal cwd, else a sentinel-free absolute TERMINAL_CWD (the worktree path cli.py/main.py set for -w). _resolve_base_dir() and _path_resolution_warning() both use it, so a worktree session resolves into — and warns about escaping — the worktree from the very first write, before any cd has run. Validation: 11 new/parametrized tests (sentinel handling, empty-registry anchoring, early divergence warning, live-cwd precedence). 32/32 pass under scripts/run_tests.sh. Live E2E: relative write in an empty-registry worktree session lands in the worktree, main untouched.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A worktree dev session can no longer silently misroute a relative
write_file/patchinto the main checkout — sentinel/relativeTERMINAL_CWDvalues are rejected as anchors, and the worktree is used (and divergence is warned about) from the very first write, before any terminal command has run.This completes the fix from #35399. That PR made the misroute visible (it added
resolved_pathto every write), but it did not prevent it: its divergence warning only fired once a terminal command had populated the live-cwd registry. A fresh worktree session — registry still empty,TERMINAL_CWDleft at the stale sentinel"."— got neither a worktree anchor nor a warning, so a relative edit landed in main, self-verified, and reported success against the wrong file. (Hit live this session.)Root cause
_resolve_base_dir()treated a relativeTERMINAL_CWD(commonly".") as a literal base and joined it onto the agent's process cwd — which, in a-wsession, is the main repo. With no live terminal cwd yet recorded, the relative edit resolved into main._path_resolution_warning()returned early whenever the live cwd was unknown, so nothing flagged it.Changes (
tools/file_tools.py)'',.,./,auto,cwd(and any relative value) are treated as unset, never as a literal anchor. The gateway already sanitizes this set at import time (gateway/run.py); the file-tool layer now matches, so CLI sessions get the same protection._authoritative_workspace_root(): prefers the live terminal cwd, else a sentinel-free absoluteTERMINAL_CWD(the worktree pathcli.py/main.pyset for-w). Both_resolve_base_dir()and_path_resolution_warning()use it — so a worktree session resolves into, and warns about escaping, the worktree from the first write._resolve_base_dir()falls through to the process cwd only as a deterministic last resort.Validation
TERMINAL_CWD='.'+ relative editTERMINAL_CWD=<abs worktree>+ relative edit_warningfires on first writetests/tools/test_file_tools_cwd_resolution.py.scripts/run_tests.sh(per-file process isolation = CI condition).write_filein an empty-registry worktree session lands in the worktree; main untouched.Note:
tests/tools/test_terminal_task_cwd.pyhas 3 failures that are pre-existing onmain(confirmed by stashing this change) and unrelated to this PR — they assert afake_env.cwdmutation interminal_tool.py, not file-tool resolution. Flagging for a separate fix.Infographic