# NousResearch/hermes-agent · Redundant temp-file read in LocalEnvironment._update_cwd

## Open now

- Nothing open on GitHub.

A bug report and multiple fix attempts addressing unnecessary file I/O in LocalEnvironment._update_cwd(), which was writing and then immediately re-reading a temp file on every terminal() call. Several approaches are represented, including removing the read, parsing cwd from stdout, and dropping the temp-file write entirely.

## TL;DR

- 8 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 6 PR(s) closed without merging.
- A fix was merged to main in PR #65483. Verify it addresses your case.
- **Recommended:** read PR #65483 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-65483`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.25`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #63225: \[Bug\]: \`LocalEnvironment._update_cwd()\` reads back a temp file it just wrote — redundant file I/O on every \`terminal()\` call

## Prior & competing PRs

- **#64095: fix(local): remove redundant temp-file read from LocalEnvironment._update_cwd()**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#63255`, `#63225`, `#63255`, `#63445`, `#63615`, `#63947`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#65483: fix(local): parse cwd from the stdout marker, drop the temp-file write**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `tests/tools/test_base_environment.py`, `tests/tools/test_local_env_cwd_recovery.py`, `tests/tools/test_local_env_windows_msys.py`, `tools/environments/base.py`, `tools/environments/local.py`
  - fix hunks:
  - `tests/tools/test_base_environment.py`

    ```diff
    @@ -68,7 +68,8 @@ def test_basic_shape(self):
             assert "eval 'echo hello'" in wrapped
             assert "__hermes_ec=$?" in wrapped
             assert "export -p >" in wrapped
    -        assert "pwd -P >" in wrapped
    +        # cwd travels via the stdout marker only — no temp-file write.
    +        assert "pwd -P >" not in wrapped
             assert env._cwd_marker in wrapped
             assert "exit $__hermes_ec" in wrapped
     
    ```

    ```diff
    @@ -357,7 +358,9 @@ def cleanup(self):
     
                 assert stat.S_IMODE(user_file.stat().st_mode) == 0o644
                 assert stat.S_IMODE(Path(env._snapshot_path).stat().st_mode) == 0o600
    -            assert stat.S_IMODE(Path(env._cwd_file).stat().st_mode) == 0o600
    +            # The cwd temp file is no longer written (cwd travels via the
    +            # stdout marker for every backend) — nothing to leak on disk.
    +            assert not Path(env._cwd_file).exists()
             finally:
                 os.umask(old_umask)
     
    ```
  - `tests/tools/test_local_env_cwd_recovery.py`

    ```diff
    @@ -160,13 +160,14 @@ def test_skips_assignment_when_marker_path_missing(self, tmp_path):
             with patch.object(LocalEnvironment, "init_session", autospec=True, return_value=None):
                 env = LocalEnvironment(cwd=str(original), timeout=10)
     
    -        # Simulate the stale-marker case: the prior command's ``pwd -P`` left
    -        # a path in the cwd file, but that path has since been deleted.
    +        # Simulate the stale-marker case: the prior command emitted a cwd
    +        # marker for a directory that has since been deleted.
             deleted = tmp_path / "wedge-repro"
    -        with open(env._cwd_file, "w") as f:
    -            f.write(str(deleted))
    +        marker = env._cwd_marker
     
    -        env._update_cwd({"output": "", "returncode": 0})
    +        env._update_cwd(
    +            {"output": f"x\n{marker}{deleted}{marker}\n", "returncode": 0}
    +        )
     
             assert env.cwd == str(original)
     
    ```

    ```diff
    @@ -178,10 +179,10 @@ def test_accepts_assignment_when_marker_path_exists(self, tmp_path):
     
             with patch.object(LocalEnvironment, "init_session", autospec=True, return_value=None):
                 env = LocalEnvironment(cwd=str(original), timeout=10)
    +        marker = env._cwd_marker
     
    -        with open(env._cwd_file, "w") as f:
    -            f.write(str(new_dir))
    -
    -        env._update_cwd({"output": "", "returncode": 0})
    +        env._update_cwd(
    +            {"output": f"x\n{marker}{new_dir}{marker}\n", "returncode": 0}
    +        )
     
             assert env.cwd == str(new_dir)
    ```
  - `tests/tools/test_local_env_windows_msys.py`

    ```diff
    @@ -180,15 +180,15 @@ def test_msys_path_resolves_to_native_when_native_exists(
     
     
     # ---------------------------------------------------------------------------
    -# End-to-end: _update_cwd via marker file (Windows simulation)
    +# End-to-end: _update_cwd via stdout marker (Windows simulation)
     # ---------------------------------------------------------------------------
     
     class TestUpdateCwdWindowsMsys:
    -    def test_marker_file_msys_path_stored_in_native_form(
    +    def test_marker_output_msys_path_stored_in_native_form(
             self, monkeypatch, tmp_path,
         ):
    -        """When Git Bash writes ``/c/Users/x`` to the cwd marker file on
    -        Windows, ``_update_cwd`` must translate to native form before
    +        """When Git Bash emits ``/c/Users/x`` in the cwd marker on Windows,
    +        ``_update_cwd`` must translate to native form before
             validating and storing — otherwise ``os.path.isdir`` rejects a
             perfectly real directory."""
             original = tmp_path / "starting"
    ```

    ```diff
    @@ -205,18 +205,21 @@ def test_marker_file_msys_path_stored_in_native_form(
             # Pretend Git Bash wrote an MSYS path that maps to tmp_path/"next"
             new_dir = tmp_path / "next"
             new_dir.mkdir()
    +        marker = env._cwd_marker
     
    -        with open(env._cwd_file, "w") as f:
    -            f.write("/c/whatever/from/bash")
    -
    -        # Translate the synthetic MSYS string to the real native dir.
    +        # Translate the synthetic MSYS marker path to the real native dir.
             def fake_translate(p):
                 if p == "/c/whatever/from/bash":
                     return str(new_dir)
                 return p
     
             with patch.object(local_mod, "_msys_to_windows_path", side_effect=fake_translate):
    -            env._update_cwd({"output": "", "returncode": 0})
    +            env._update_cwd(
    +                {
    +                    "output": f"x\n{marker}/c/whatever/from/bash{marker}\n",
    +                    "returncode": 0,
    +                }
    +            )
     
             assert env.cwd == str(new_dir)
     
    ```
  - `tools/environments/base.py`

    ```diff
    @@ -478,7 +478,6 @@ def init_session(self):
             # ``Directory \\drivers\\etc does not exist`` failure class.
             # On POSIX this is plain ``shlex.quote``.
             _quoted_snap = self._quote_shell_path(self._snapshot_path)
    -        _quoted_cwd_file = self._quote_shell_path(self._cwd_file)
             # Use atomic file replacement: assemble the snapshot in a temp file,
             # then mv it over the final path.  This prevents concurrent source()
             # calls from reading a half-written snapshot when another terminal
    ```

    ```diff
    @@ -523,7 +522,6 @@ def init_session(self):
                 # partial temp rather than leave it to be sourced or orphaned.
                 f"mv -f {_snap_tmp} {_quoted_snap} || rm -f {_snap_tmp}\n"
                 f"builtin cd -- {_quoted_cwd} 2>/dev/null || true\n"
    -            f"pwd -P > {_quoted_cwd_file} 2>/dev/null || true\n"
                 f"printf '\\n{self._cwd_marker}%s{self._cwd_marker}\\n' \"$(pwd -P)\"\n"
             )
             try:
    ```

    ```diff
    @@ -602,10 +600,9 @@ def _wrap_command(self, command: str, cwd: str) -> str:
             re-dumps env vars, and emits CWD markers."""
             escaped = command.replace("'", "'\\''")
     
    -        # Quote snapshot/cwd-file paths (see init_session — LocalEnvironment
    -        # rewrites ``C:/...`` to ``/c/...`` so MSYS doesn't mangle them).
    +        # Quote the snapshot path (see init_session — LocalEnvironment
    +        # rewrites ``C:/...`` to ``/c/...`` so MSYS doesn't mangle it).
             _quoted_snap = self._quote_shell_path(self._snapshot_path)
    -        _quoted_cwd_file = self._quote_shell_path(self._cwd_file)
             # Use atomic file replacement for env snapshot updates (issue #38249).
             # Assemble into a per-writer-unique temp file, then mv to atomically
             # replace the snapshot so concurrent source() calls never read a
    ```

    ```diff
    @@ -651,8 +648,8 @@ def _wrap_command(self, command: str, cwd: str) -> str:
                     f"2>/dev/null || rm -f {_snap_tmp} 2>/dev/null || true"
                 )
     
    -        # Write CWD to file (local reads this) and stdout marker (remote parses this)
    -        parts.append(f"pwd -P > {_quoted_cwd_file} 2>/dev/null || true")
    +        # Emit the CWD stdout marker; all backends (including local, since
    +        # PR #63255) parse it from output — no temp-file write needed.
             # Use a distinct line for the marker. The leading \n ensures
             # the marker starts on its own line even if the command doesn't
             # end with a newline (e.g. printf 'exact'). We'll strip this
    ```
  - `tools/environments/local.py`

    ```diff
    @@ -1314,31 +1314,14 @@ def _wait_for_group_exit(pgid: int, timeout: float) -> bool:
                     pass
     
         def _update_cwd(self, result: dict):
    -        """Read CWD from temp file (local-only, no round-trip needed).
    -
    -        Skip the assignment when the path no longer exists as a directory —
    -        ``pwd -P`` on a deleted cwd can leave a stale value in the marker
    -        file, and propagating it would re-wedge the next ``Popen``.  The
    -        ``_run_bash`` recovery path will resolve a safe fallback if needed.
    -
    -        On Windows, the value written by Git Bash's ``pwd -P`` is in
    -        MSYS form (``/c/Users/x``). Translate it to native Windows form
    -        before validating with ``os.path.isdir`` and before storing on
    -        ``self.cwd``; otherwise the isdir check rejects every valid
    -        result and ``_run_bash`` later prints a misleading "cwd is
    -        missing" warning on every command.
    -        """
    -        try:
    -            with open(self._cwd_file, encoding="utf-8") as f:
    -                cwd_path = f.read().strip()
    -            if _IS_WINDOWS:
    -                cwd_path = _msys_to_windows_path(cwd_path)
    -            if cwd_path and os.path.isdir(cwd_path):
    -                self.cwd = cwd_path
    -        except (OSError, FileNotFoundError):
    -            pass
    +        """Update cwd from the stdout marker emitted by the wrapped command.
     
    -        # Still strip the marker from output so it's not visible
    +        The base command wrapper already appends ``pwd -P`` to stdout inside a
    +        session-specific marker, so the local backend can share the same parser
    +        as remote backends instead of re-reading the temp file it just wrote.
    +        ``_extract_cwd_from_output`` keeps the local Windows normalization and
    +        stale-path rollback semantics intact.
    +        """
             self._extract_cwd_from_output(result)
     
         def _extract_cwd_from_output(self, result: dict):
    ```
- **#63255: fix(local): stop re-reading cwd marker file**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `0ccd05b78`, `#65483`
- **#63445: fix(local): remove redundant temp-file read in _update_cwd**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`
- **#63615: fix(local): remove redundant temp-file read from \`LocalEnvironment._update_cwd\`**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`
- **#63947: fix(local-env): remove redundant temp-file read in _update_cwd (#63225)**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`
- **#64128: fix(local): remove redundant temp-file read in _update_cwd**. closed without merge
  - maintainer direction: category `uncategorized`; implication `needs_llm`

## Fix status

- Merged to main in PR #65483 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-07-16`.
