# NousResearch/hermes-agent · Docker file_tools container config missing cwd flags

## Open now

- Nothing open on GitHub.

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.

## TL;DR

- 10 member(s): 0 open issue(s), 2 closed issue(s), 0 open PR(s), 2 merged PR(s), 6 PR(s) closed without merging.
- A fix was merged to main in PR #12900. Verify it addresses your case.
- **Recommended:** read PR #12900 (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-12900,pr-54616`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
- Members outside that structural core may belong to a different underlying problem.
- Pairwise link density `0.19`: 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

- #2672: \[Bug\]: docker_mount_cwd_to_workspace config flag does not take effect in file tools
- #54447: \[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

## Prior & competing PRs

- **#12900: fix(docker): pass docker_mount_cwd_to_workspace and docker_forward_env to container_config in file_tools**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `tests/tools/test_file_tools_container_config.py`, `tools/file_tools.py`
- **#54616: fix(file-tools): sanitize host/relative cwd override before it reaches container sandbox (#54447)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `tests/tools/test_container_cwd_sanitize.py`, `tools/file_tools.py`
  - fix hunks:
  - `tests/tools/test_container_cwd_sanitize.py`

    ```diff
    @@ -144,3 +144,114 @@ def test_valid_container_override_is_preserved(self, monkeypatch):
             # RL/benchmark envs set an in-container path; it must pass through.
             cwd = self._run_and_capture_cwd(monkeypatch, "/workspace/task42")
             assert cwd == "/workspace/task42"
    +
    +
    +class TestFileOpsCwdSanitizedAtCallSite:
    +    """E2E pin: file tools (_get_file_ops) must sanitize a host/relative cwd
    +    override before it reaches _create_environment on a container backend —
    +    the same guard the terminal tool got in #50636.  Without it, a Desktop/TUI
    +    host cwd (e.g. ``/Users/me/workspace``) leaks straight into
    +    ``docker run -w`` and ``search_files`` returns an empty workspace (#54447).
    +    """
    +
    +    def _run_and_capture_cwd(self, monkeypatch, override_cwd, env_type="docker",
    +                             config_cwd="/workspace"):
    +        """Drive ``_get_file_ops()`` on a container backend with a host-path cwd
    +        override registered, and return the cwd that reached
    +        ``_create_environment`` (i.e. the cwd passed to ``docker run -w``).
    +        """
    +        import tools.terminal_tool as tt
    +        import tools.file_tools as ft
    +
    +        captured = {}
    +
    +        config = {
    +            "env_type": env_type,
    +            "docker_image": "pytorch/pytorch:latest",
    +            "singularity_image": "docker://pytorch/pytorch:latest",
    +            "modal_image": "pytorch/pytorch:latest",
    +            "daytona_image": "pytorch/pytorch:latest",
    +            "cwd": config_cwd,
    +            "host_cwd": None,
    +            "timeout": 180,
    +            "lifetime_seconds": 300,
    +            "container_cpu": 1,
    +            "container_memory": 5120,
    +            "container_disk": 51200,
    +            "container_persistent": True,
    +            "docker_volumes": [],
    +            "docker_env": {},
    +            "docker_extra_args": [],
    +            "docker_mount_cwd_to_workspace": False,
    +            "docker_run_as_host_user": False,
    +            "docker_forward_env": [],
    +            "modal_mode": "auto",
    +            "ssh_host": "",
    +            "ssh_user": "",
    +            "ssh_port": 22,
    +            "ssh_key": "",
    +            "ssh_persistent": False,
    +            "local_persistent": False,
    +        }
    +
    +        class _DummyEnv:
    +            cwd = config_cwd
    +
    +            def execute(self, *a, **k):
    +                return {"output": "", "exit_code": 0}
    +
    +        def fake_create_environment(env_type, image, cwd, timeout, **kwargs):
    +            captured["cwd"] = cwd
    +            return _DummyEnv()
    +
    +        monkeypatch.setattr(tt, "_get_env_config", lambda: config)
    +        monkeypatch.setattr(tt, "_start_cleanup_thread", lambda: None)
    +        monkeypatch.setattr(tt, "_create_environment", fake_create_environment)
    +        # Force a fresh environment build.
    +        monkeypatch.setattr(tt, "_active_environments", {})
    +        monkeypatch.setattr(tt, "_last_activity", {})
    +        monkeypatch.setattr(ft, "_file_ops_cache", {})
    +        monkeypatch.setattr(ft, "_last_known_cwd", {})
    +
    +        task_id = "sess-fileops-host-cwd"
    +        tt.register_task_env_overrides(task_id, {"cwd": override_cwd})
    +        try:
    +            ft._get_file_ops(task_id)
    +        finally:
    +            tt.clear_task_env_overrides(task_id)
    +        return captured.get("cwd")
    +
    +    def test_macos_host_override_does_not_reach_container(self, monkeypatch):
    +        # Desktop/TUI registers /Users/<me>/workspace as the session cwd.
    +        cwd = self._run_and_capture_cwd(monkeypatch, "/Users/me/workspace")
    +        assert cwd == "/workspace", (
    +            f"Host-path cwd override leaked to the container builder: {cwd!r}. "
    +            "It must be sanitized back to config['cwd']."
    +        )
    +
    +    def test_posix_home_host_override_does_not_reach_container(self, monkeypatch):
    +        cwd = self._run_and_capture_cwd(monkeypatch, "/home/someuser/project")
    +        assert cwd == "/workspace"
    +
    +    def test_windows_host_override_does_not_reach_container(self, monkeypatch):
    +        cwd = self._run_and_capture_cwd(monkeypatch, r"C:\Users\someuser")
    +        assert cwd == "/workspace"
    +
    +    def test_relative_cwd_override_does_not_reach_container(self, monkeypatch):
    +        cwd = self._run_and_capture_cwd(monkeypatch, "src/app")
    +        assert cwd == "/workspace"
    +
    +    def test_valid_container_override_is_preserved(self, monkeypatch):
    +        # RL/benchmark envs set an in-container path; it must pass through.
    +        cwd = self._run_and_capture_cwd(monkeypatch, "/workspace/task42")
    +        assert cwd == "/workspace/task42"
    +
    +    def test_host_override_sanitized_on_singularity(self, monkeypatch):
    +        cwd = self._run_and_capture_cwd(
    +            monkeypatch, "/Users/me/workspace", env_type="singularity")
    +        assert cwd == "/workspace"
    +
    +    def test_host_override_sanitized_on_modal(self, monkeypatch):
    +        cwd = self._run_and_capture_cwd(
    +            monkeypatch, "/Users/me/workspace", env_type="modal")
    +        assert cwd == "/workspace"
    ```
  - `tools/file_tools.py`

    ```diff
    @@ -832,6 +832,8 @@ def _get_file_ops(task_id: str = "default") -> ShellFileOperations:
             _creation_locks,
             _creation_locks_lock,
             _resolve_container_task_id,
    +        _is_unusable_container_cwd,
    +        _CONTAINER_BACKENDS,
         )
         import time
     
    ```

    ```diff
    @@ -893,6 +895,26 @@ def _get_file_ops(task_id: str = "default") -> ShellFileOperations:
                     image = ""
     
                 cwd = overrides.get("cwd") or _last_known_cwd.get(task_id) or config["cwd"]
    +            # Re-apply the container cwd guard that _get_env_config() already
    +            # ran on config["cwd"] (see #50636).  A per-task cwd override
    +            # registered by the gateway/TUI/ACP for workspace tracking is a
    +            # raw host path (e.g. a Desktop session's /Users/<me>/workspace or
    +            # C:\\Users\\<me>). On a container backend that reaches
    +            # ``docker run -w <host-path>`` and the container starts in a
    +            # directory that doesn't exist inside the sandbox, so search_files
    +            # and friends silently return empty results (#54447).  Sanitize it
    +            # back to the already-validated config["cwd"] so the override can't
    +            # bypass the guard.  Valid in-container override paths (RL/benchmark
    +            # sandboxes that set cwd to /workspace, /root, etc.) are absolute
    +            # non-host paths and pass through untouched.
    +            if env_type in _CONTAINER_BACKENDS and _is_unusable_container_cwd(cwd):
    +                if cwd != config["cwd"]:
    +                    logger.info(
    +                        "Ignoring host/relative cwd override %r for %s backend "
    +                        "(won't exist in sandbox). Using %r instead.",
    +                        cwd, env_type, config["cwd"],
    +                    )
    +                cwd = config["cwd"]
                 logger.info("Creating new %s environment for task %s...", env_type, task_id[:8])
     
                 container_config = None
    ```
- **#2674: fix(file_tools): pass docker_mount_cwd_to_workspace to container_config**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#12900`
- **#2688: fix(docker): pass docker_mount_cwd_to_workspace and docker_forward_env to container_config in file_tools**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `2d59afd3d`, `2d59afd3da04812583ecbbf3be83539678a947c4`
- **#2697: fix: pass docker_mount_cwd_to_workspace to container_config in file_tools**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#12900`
- **#2729: fix(file_tools): pass docker_mount_cwd_to_workspace to container_config**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#12900`
- **#11888: fix: docker_mount_cwd_to_workspace missing from file_tools container_config**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#12900`
- **#54474: fix(file-tools): sanitize host/relative cwd override before it reaches container sandbox (#54447)**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#54616`, `#54616`

## Fix status

- Merged to main in PR #12900 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-29`.
- Subsystem: `environments`
