{"repo":"NousResearch/hermes-agent","public_id":"2672","display_label":"Docker file_tools container config missing cwd flags","summary":"Multiple reports and fix attempts address docker_mount_cwd_to_workspace and docker_forward_env not being passed to container_config in file_tools, plus a related issue of unsanitized host cwd reaching the container sandbox.","subsystem":"environments","counts":{"members":10,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":2,"merged_prs":2,"attempt_prs":6},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":12900},"resolution":{"state":"resolved","by":"pr-12900,pr-54616"},"freshness":{"state":"verified","last_verified_at":"2026-06-29T05:32:20+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.18577777777777776},"fix_edges":[{"issue_number":2672,"pr_number":12900,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":54447,"pr_number":54616,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":2672,"title":"[Bug]: docker_mount_cwd_to_workspace config flag does not take effect in file tools","url":null,"state":"closed"},{"kind":"issue","number":54447,"title":"[Bug]: file tools build their Docker sandbox with an un-sanitized host cwd (search_files sees empty workspace); terminal-side fix #50636 left file_tools.py untouched","url":null,"state":"closed"},{"kind":"pr","number":2674,"title":"fix(file_tools): pass docker_mount_cwd_to_workspace to container_config","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#12900","number":12900}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":2688,"title":"fix(docker): pass docker_mount_cwd_to_workspace and docker_forward_env to container_config in file_tools","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"2d59afd3d","number":0},{"kind":"commit","value":"2d59afd3da04812583ecbbf3be83539678a947c4","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":2697,"title":"fix: pass docker_mount_cwd_to_workspace to container_config in file_tools","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#12900","number":12900}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":2729,"title":"fix(file_tools): pass docker_mount_cwd_to_workspace to container_config","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#12900","number":12900}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":11888,"title":"fix: docker_mount_cwd_to_workspace missing from file_tools container_config","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#12900","number":12900}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":12900,"title":"fix(docker): pass docker_mount_cwd_to_workspace and docker_forward_env to container_config in file_tools","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tests/tools/test_file_tools_container_config.py","tools/file_tools.py"]},{"kind":"pr","number":54474,"title":"fix(file-tools): sanitize host/relative cwd override before it reaches container sandbox (#54447)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#54616","number":54616},{"kind":"pr","value":"#54616","number":54616}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":54616,"title":"fix(file-tools): sanitize host/relative cwd override before it reaches container sandbox (#54447)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tests/tools/test_container_cwd_sanitize.py","tools/file_tools.py"],"fix_hunks":[{"file":"tests/tools/test_container_cwd_sanitize.py","hunks":["@@ -144,3 +144,114 @@ def test_valid_container_override_is_preserved(self, monkeypatch):\n         # RL/benchmark envs set an in-container path; it must pass through.\n         cwd = self._run_and_capture_cwd(monkeypatch, \"/workspace/task42\")\n         assert cwd == \"/workspace/task42\"\n+\n+\n+class TestFileOpsCwdSanitizedAtCallSite:\n+    \"\"\"E2E pin: file tools (_get_file_ops) must sanitize a host/relative cwd\n+    override before it reaches _create_environment on a container backend —\n+    the same guard the terminal tool got in #50636.  Without it, a Desktop/TUI\n+    host cwd (e.g. ``/Users/me/workspace``) leaks straight into\n+    ``docker run -w`` and ``search_files`` returns an empty workspace (#54447).\n+    \"\"\"\n+\n+    def _run_and_capture_cwd(self, monkeypatch, override_cwd, env_type=\"docker\",\n+                             config_cwd=\"/workspace\"):\n+        \"\"\"Drive ``_get_file_ops()`` on a container backend with a host-path cwd\n+        override registered, and return the cwd that reached\n+        ``_create_environment`` (i.e. the cwd passed to ``docker run -w``).\n+        \"\"\"\n+        import tools.terminal_tool as tt\n+        import tools.file_tools as ft\n+\n+        captured = {}\n+\n+        config = {\n+            \"env_type\": env_type,\n+            \"docker_image\": \"pytorch/pytorch:latest\",\n+            \"singularity_image\": \"docker://pytorch/pytorch:latest\",\n+            \"modal_image\": \"pytorch/pytorch:latest\",\n+            \"daytona_image\": \"pytorch/pytorch:latest\",\n+            \"cwd\": config_cwd,\n+            \"host_cwd\": None,\n+            \"timeout\": 180,\n+            \"lifetime_seconds\": 300,\n+            \"container_cpu\": 1,\n+            \"container_memory\": 5120,\n+            \"container_disk\": 51200,\n+            \"container_persistent\": True,\n+            \"docker_volumes\": [],\n+            \"docker_env\": {},\n+            \"docker_extra_args\": [],\n+            \"docker_mount_cwd_to_workspace\": False,\n+            \"docker_run_as_host_user\": False,\n+            \"docker_forward_env\": [],\n+            \"modal_mode\": \"auto\",\n+            \"ssh_host\": \"\",\n+            \"ssh_user\": \"\",\n+            \"ssh_port\": 22,\n+            \"ssh_key\": \"\",\n+            \"ssh_persistent\": False,\n+            \"local_persistent\": False,\n+        }\n+\n+        class _DummyEnv:\n+            cwd = config_cwd\n+\n+            def execute(self, *a, **k):\n+                return {\"output\": \"\", \"exit_code\": 0}\n+\n+        def fake_create_environment(env_type, image, cwd, timeout, **kwargs):\n+            captured[\"cwd\"] = cwd\n+            return _DummyEnv()\n+\n+        monkeypatch.setattr(tt, \"_get_env_config\", lambda: config)\n+        monkeypatch.setattr(tt, \"_start_cleanup_thread\", lambda: None)\n+        monkeypatch.setattr(tt, \"_create_environment\", fake_create_environment)\n+        # Force a fresh environment build.\n+        monkeypatch.setattr(tt, \"_active_environments\", {})\n+        monkeypatch.setattr(tt, \"_last_activity\", {})\n+        monkeypatch.setattr(ft, \"_file_ops_cache\", {})\n+        monkeypatch.setattr(ft, \"_last_known_cwd\", {})\n+\n+        task_id = \"sess-fileops-host-cwd\"\n+        tt.register_task_env_overrides(task_id, {\"cwd\": override_cwd})\n+        try:\n+            ft._get_file_ops(task_id)\n+        finally:\n+            tt.clear_task_env_overrides(task_id)\n+        return captured.get(\"cwd\")\n+\n+    def test_macos_host_override_does_not_reach_container(self, monkeypatch):\n+        # Desktop/TUI registers /Users/<me>/workspace as the session cwd.\n+        cwd = self._run_and_capture_cwd(monkeypatch, \"/Users/me/workspace\")\n+        assert cwd == \"/workspace\", (\n+            f\"Host-path cwd override leaked to the container builder: {cwd!r}. \"\n+            \"It must be sanitized back to config['cwd'].\"\n+        )\n+\n+    def test_posix_home_host_override_does_not_reach_container(self, monkeypatch):\n+        cwd = self._run_and_capture_cwd(monkeypatch, \"/home/someuser/project\")\n+        assert cwd == \"/workspace\"\n+\n+    def test_windows_host_override_does_not_reach_container(self, monkeypatch):\n+        cwd = self._run_and_capture_cwd(monkeypatch, r\"C:\\Users\\someuser\")\n+        assert cwd == \"/workspace\"\n+\n+    def test_relative_cwd_override_does_not_reach_container(self, monkeypatch):\n+        cwd = self._run_and_capture_cwd(monkeypatch, \"src/app\")\n+        assert cwd == \"/workspace\"\n+\n+    def test_valid_container_override_is_preserved(self, monkeypatch):\n+        # RL/benchmark envs set an in-container path; it must pass through.\n+        cwd = self._run_and_capture_cwd(monkeypatch, \"/workspace/task42\")\n+        assert cwd == \"/workspace/task42\"\n+\n+    def test_host_override_sanitized_on_singularity(self, monkeypatch):\n+        cwd = self._run_and_capture_cwd(\n+            monkeypatch, \"/Users/me/workspace\", env_type=\"singularity\")\n+        assert cwd == \"/workspace\"\n+\n+    def test_host_override_sanitized_on_modal(self, monkeypatch):\n+        cwd = self._run_and_capture_cwd(\n+            monkeypatch, \"/Users/me/workspace\", env_type=\"modal\")\n+        assert cwd == \"/workspace\""]},{"file":"tools/file_tools.py","hunks":["@@ -832,6 +832,8 @@ def _get_file_ops(task_id: str = \"default\") -> ShellFileOperations:\n         _creation_locks,\n         _creation_locks_lock,\n         _resolve_container_task_id,\n+        _is_unusable_container_cwd,\n+        _CONTAINER_BACKENDS,\n     )\n     import time\n ","@@ -893,6 +895,26 @@ def _get_file_ops(task_id: str = \"default\") -> ShellFileOperations:\n                 image = \"\"\n \n             cwd = overrides.get(\"cwd\") or _last_known_cwd.get(task_id) or config[\"cwd\"]\n+            # Re-apply the container cwd guard that _get_env_config() already\n+            # ran on config[\"cwd\"] (see #50636).  A per-task cwd override\n+            # registered by the gateway/TUI/ACP for workspace tracking is a\n+            # raw host path (e.g. a Desktop session's /Users/<me>/workspace or\n+            # C:\\\\Users\\\\<me>). On a container backend that reaches\n+            # ``docker run -w <host-path>`` and the container starts in a\n+            # directory that doesn't exist inside the sandbox, so search_files\n+            # and friends silently return empty results (#54447).  Sanitize it\n+            # back to the already-validated config[\"cwd\"] so the override can't\n+            # bypass the guard.  Valid in-container override paths (RL/benchmark\n+            # sandboxes that set cwd to /workspace, /root, etc.) are absolute\n+            # non-host paths and pass through untouched.\n+            if env_type in _CONTAINER_BACKENDS and _is_unusable_container_cwd(cwd):\n+                if cwd != config[\"cwd\"]:\n+                    logger.info(\n+                        \"Ignoring host/relative cwd override %r for %s backend \"\n+                        \"(won't exist in sandbox). Using %r instead.\",\n+                        cwd, env_type, config[\"cwd\"],\n+                    )\n+                cwd = config[\"cwd\"]\n             logger.info(\"Creating new %s environment for task %s...\", env_type, task_id[:8])\n \n             container_config = None\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}