{"repo":"NousResearch/hermes-agent","public_id":"63225","display_label":"Redundant temp-file read in LocalEnvironment._update_cwd","summary":"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.","subsystem":null,"counts":{"members":8,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":6},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":65483},"resolution":{"state":"resolved","by":"pr-65483"},"freshness":{"state":"verified","last_verified_at":"2026-07-16T08:28:24+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.25},"fix_edges":[{"issue_number":63225,"pr_number":65483,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":63225,"title":"[Bug]: `LocalEnvironment._update_cwd()` reads back a temp file it just wrote — redundant file I/O on every `terminal()` call","url":null,"state":"closed"},{"kind":"pr","number":63255,"title":"fix(local): stop re-reading cwd marker file","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"0ccd05b78","number":0},{"kind":"pr","value":"#65483","number":65483}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":63445,"title":"fix(local): remove redundant temp-file read in _update_cwd","url":null,"state":"closed","close_reason":{"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":63615,"title":"fix(local): remove redundant temp-file read from `LocalEnvironment._update_cwd`","url":null,"state":"closed","close_reason":{"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":63947,"title":"fix(local-env): remove redundant temp-file read in _update_cwd (#63225)","url":null,"state":"closed","close_reason":{"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":64095,"title":"fix(local): remove redundant temp-file read from LocalEnvironment._update_cwd()","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#63255","number":63255},{"kind":"pr","value":"#63225","number":63225},{"kind":"pr","value":"#63255","number":63255},{"kind":"pr","value":"#63445","number":63445},{"kind":"pr","value":"#63615","number":63615},{"kind":"pr","value":"#63947","number":63947}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":64128,"title":"fix(local): remove redundant temp-file read in _update_cwd","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":65483,"title":"fix(local): parse cwd from the stdout marker, drop the temp-file write","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["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":[{"file":"tests/tools/test_base_environment.py","hunks":["@@ -68,7 +68,8 @@ def test_basic_shape(self):\n         assert \"eval 'echo hello'\" in wrapped\n         assert \"__hermes_ec=$?\" in wrapped\n         assert \"export -p >\" in wrapped\n-        assert \"pwd -P >\" in wrapped\n+        # cwd travels via the stdout marker only — no temp-file write.\n+        assert \"pwd -P >\" not in wrapped\n         assert env._cwd_marker in wrapped\n         assert \"exit $__hermes_ec\" in wrapped\n ","@@ -357,7 +358,9 @@ def cleanup(self):\n \n             assert stat.S_IMODE(user_file.stat().st_mode) == 0o644\n             assert stat.S_IMODE(Path(env._snapshot_path).stat().st_mode) == 0o600\n-            assert stat.S_IMODE(Path(env._cwd_file).stat().st_mode) == 0o600\n+            # The cwd temp file is no longer written (cwd travels via the\n+            # stdout marker for every backend) — nothing to leak on disk.\n+            assert not Path(env._cwd_file).exists()\n         finally:\n             os.umask(old_umask)\n "]},{"file":"tests/tools/test_local_env_cwd_recovery.py","hunks":["@@ -160,13 +160,14 @@ def test_skips_assignment_when_marker_path_missing(self, tmp_path):\n         with patch.object(LocalEnvironment, \"init_session\", autospec=True, return_value=None):\n             env = LocalEnvironment(cwd=str(original), timeout=10)\n \n-        # Simulate the stale-marker case: the prior command's ``pwd -P`` left\n-        # a path in the cwd file, but that path has since been deleted.\n+        # Simulate the stale-marker case: the prior command emitted a cwd\n+        # marker for a directory that has since been deleted.\n         deleted = tmp_path / \"wedge-repro\"\n-        with open(env._cwd_file, \"w\") as f:\n-            f.write(str(deleted))\n+        marker = env._cwd_marker\n \n-        env._update_cwd({\"output\": \"\", \"returncode\": 0})\n+        env._update_cwd(\n+            {\"output\": f\"x\\n{marker}{deleted}{marker}\\n\", \"returncode\": 0}\n+        )\n \n         assert env.cwd == str(original)\n ","@@ -178,10 +179,10 @@ def test_accepts_assignment_when_marker_path_exists(self, tmp_path):\n \n         with patch.object(LocalEnvironment, \"init_session\", autospec=True, return_value=None):\n             env = LocalEnvironment(cwd=str(original), timeout=10)\n+        marker = env._cwd_marker\n \n-        with open(env._cwd_file, \"w\") as f:\n-            f.write(str(new_dir))\n-\n-        env._update_cwd({\"output\": \"\", \"returncode\": 0})\n+        env._update_cwd(\n+            {\"output\": f\"x\\n{marker}{new_dir}{marker}\\n\", \"returncode\": 0}\n+        )\n \n         assert env.cwd == str(new_dir)"]},{"file":"tests/tools/test_local_env_windows_msys.py","hunks":["@@ -180,15 +180,15 @@ def test_msys_path_resolves_to_native_when_native_exists(\n \n \n # ---------------------------------------------------------------------------\n-# End-to-end: _update_cwd via marker file (Windows simulation)\n+# End-to-end: _update_cwd via stdout marker (Windows simulation)\n # ---------------------------------------------------------------------------\n \n class TestUpdateCwdWindowsMsys:\n-    def test_marker_file_msys_path_stored_in_native_form(\n+    def test_marker_output_msys_path_stored_in_native_form(\n         self, monkeypatch, tmp_path,\n     ):\n-        \"\"\"When Git Bash writes ``/c/Users/x`` to the cwd marker file on\n-        Windows, ``_update_cwd`` must translate to native form before\n+        \"\"\"When Git Bash emits ``/c/Users/x`` in the cwd marker on Windows,\n+        ``_update_cwd`` must translate to native form before\n         validating and storing — otherwise ``os.path.isdir`` rejects a\n         perfectly real directory.\"\"\"\n         original = tmp_path / \"starting\"","@@ -205,18 +205,21 @@ def test_marker_file_msys_path_stored_in_native_form(\n         # Pretend Git Bash wrote an MSYS path that maps to tmp_path/\"next\"\n         new_dir = tmp_path / \"next\"\n         new_dir.mkdir()\n+        marker = env._cwd_marker\n \n-        with open(env._cwd_file, \"w\") as f:\n-            f.write(\"/c/whatever/from/bash\")\n-\n-        # Translate the synthetic MSYS string to the real native dir.\n+        # Translate the synthetic MSYS marker path to the real native dir.\n         def fake_translate(p):\n             if p == \"/c/whatever/from/bash\":\n                 return str(new_dir)\n             return p\n \n         with patch.object(local_mod, \"_msys_to_windows_path\", side_effect=fake_translate):\n-            env._update_cwd({\"output\": \"\", \"returncode\": 0})\n+            env._update_cwd(\n+                {\n+                    \"output\": f\"x\\n{marker}/c/whatever/from/bash{marker}\\n\",\n+                    \"returncode\": 0,\n+                }\n+            )\n \n         assert env.cwd == str(new_dir)\n "]},{"file":"tools/environments/base.py","hunks":["@@ -478,7 +478,6 @@ def init_session(self):\n         # ``Directory \\\\drivers\\\\etc does not exist`` failure class.\n         # On POSIX this is plain ``shlex.quote``.\n         _quoted_snap = self._quote_shell_path(self._snapshot_path)\n-        _quoted_cwd_file = self._quote_shell_path(self._cwd_file)\n         # Use atomic file replacement: assemble the snapshot in a temp file,\n         # then mv it over the final path.  This prevents concurrent source()\n         # calls from reading a half-written snapshot when another terminal","@@ -523,7 +522,6 @@ def init_session(self):\n             # partial temp rather than leave it to be sourced or orphaned.\n             f\"mv -f {_snap_tmp} {_quoted_snap} || rm -f {_snap_tmp}\\n\"\n             f\"builtin cd -- {_quoted_cwd} 2>/dev/null || true\\n\"\n-            f\"pwd -P > {_quoted_cwd_file} 2>/dev/null || true\\n\"\n             f\"printf '\\\\n{self._cwd_marker}%s{self._cwd_marker}\\\\n' \\\"$(pwd -P)\\\"\\n\"\n         )\n         try:","@@ -602,10 +600,9 @@ def _wrap_command(self, command: str, cwd: str) -> str:\n         re-dumps env vars, and emits CWD markers.\"\"\"\n         escaped = command.replace(\"'\", \"'\\\\''\")\n \n-        # Quote snapshot/cwd-file paths (see init_session — LocalEnvironment\n-        # rewrites ``C:/...`` to ``/c/...`` so MSYS doesn't mangle them).\n+        # Quote the snapshot path (see init_session — LocalEnvironment\n+        # rewrites ``C:/...`` to ``/c/...`` so MSYS doesn't mangle it).\n         _quoted_snap = self._quote_shell_path(self._snapshot_path)\n-        _quoted_cwd_file = self._quote_shell_path(self._cwd_file)\n         # Use atomic file replacement for env snapshot updates (issue #38249).\n         # Assemble into a per-writer-unique temp file, then mv to atomically\n         # replace the snapshot so concurrent source() calls never read a","@@ -651,8 +648,8 @@ def _wrap_command(self, command: str, cwd: str) -> str:\n                 f\"2>/dev/null || rm -f {_snap_tmp} 2>/dev/null || true\"\n             )\n \n-        # Write CWD to file (local reads this) and stdout marker (remote parses this)\n-        parts.append(f\"pwd -P > {_quoted_cwd_file} 2>/dev/null || true\")\n+        # Emit the CWD stdout marker; all backends (including local, since\n+        # PR #63255) parse it from output — no temp-file write needed.\n         # Use a distinct line for the marker. The leading \\n ensures\n         # the marker starts on its own line even if the command doesn't\n         # end with a newline (e.g. printf 'exact'). We'll strip this"]},{"file":"tools/environments/local.py","hunks":["@@ -1314,31 +1314,14 @@ def _wait_for_group_exit(pgid: int, timeout: float) -> bool:\n                 pass\n \n     def _update_cwd(self, result: dict):\n-        \"\"\"Read CWD from temp file (local-only, no round-trip needed).\n-\n-        Skip the assignment when the path no longer exists as a directory —\n-        ``pwd -P`` on a deleted cwd can leave a stale value in the marker\n-        file, and propagating it would re-wedge the next ``Popen``.  The\n-        ``_run_bash`` recovery path will resolve a safe fallback if needed.\n-\n-        On Windows, the value written by Git Bash's ``pwd -P`` is in\n-        MSYS form (``/c/Users/x``). Translate it to native Windows form\n-        before validating with ``os.path.isdir`` and before storing on\n-        ``self.cwd``; otherwise the isdir check rejects every valid\n-        result and ``_run_bash`` later prints a misleading \"cwd is\n-        missing\" warning on every command.\n-        \"\"\"\n-        try:\n-            with open(self._cwd_file, encoding=\"utf-8\") as f:\n-                cwd_path = f.read().strip()\n-            if _IS_WINDOWS:\n-                cwd_path = _msys_to_windows_path(cwd_path)\n-            if cwd_path and os.path.isdir(cwd_path):\n-                self.cwd = cwd_path\n-        except (OSError, FileNotFoundError):\n-            pass\n+        \"\"\"Update cwd from the stdout marker emitted by the wrapped command.\n \n-        # Still strip the marker from output so it's not visible\n+        The base command wrapper already appends ``pwd -P`` to stdout inside a\n+        session-specific marker, so the local backend can share the same parser\n+        as remote backends instead of re-reading the temp file it just wrote.\n+        ``_extract_cwd_from_output`` keeps the local Windows normalization and\n+        stale-path rollback semantics intact.\n+        \"\"\"\n         self._extract_cwd_from_output(result)\n \n     def _extract_cwd_from_output(self, result: dict):\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}