{"repo":"NousResearch/hermes-agent","public_id":"12482","display_label":"WSL path translation and ACP cwd handling fixes","summary":"These issues and PRs address bugs where Windows paths were not being translated to WSL mount paths during ACP session creation, and where the terminal tool was not honoring the working directory set by ACP for foreground commands.","subsystem":null,"counts":{"members":4,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":18233,"merged_at":"2026-05-01T03:55:15+00:00"},"resolution":{"state":"resolved","by":"pr-18233"},"freshness":{"state":"verified","last_verified_at":"2026-05-01T03:55:15+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.5},"fix_edges":[{"issue_number":12482,"pr_number":18233,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-01T03:55:15+00:00"}],"members":[{"kind":"issue","number":12482,"title":"[Bug]: WSL path translation doesn't work for ACP, and terminal_tool ignores ACP cwd when running foreground commands","url":null,"state":"closed"},{"kind":"pr","number":12531,"title":"fix(acp): translate Windows cwd to WSL mount path on ACP session creation","url":null,"state":"closed","close_reason":{"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":18128,"title":"fix(acp): translate Windows cwd for WSL sessions","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"ec1443b9f","number":0},{"kind":"pr","value":"#18233","number":18233},{"kind":"pr","value":"#12531","number":12531}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":18233,"title":"fix(acp): translate Windows cwd for WSL sessions (salvage #18128)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["acp_adapter/session.py","tests/acp/test_session.py"],"fix_hunks":[{"file":"acp_adapter/session.py","hunks":["@@ -26,6 +26,33 @@\n logger = logging.getLogger(__name__)\n \n \n+def _win_path_to_wsl(path: str) -> str | None:\n+    \"\"\"Convert a Windows drive path to its WSL /mnt/<drive>/... equivalent.\"\"\"\n+    match = re.match(r\"^([A-Za-z]):[\\\\/](.*)$\", path)\n+    if not match:\n+        return None\n+    drive = match.group(1).lower()\n+    tail = match.group(2).replace(\"\\\\\", \"/\")\n+    return f\"/mnt/{drive}/{tail}\"\n+\n+\n+def _translate_acp_cwd(cwd: str) -> str:\n+    \"\"\"Translate Windows ACP cwd values when Hermes itself is running in WSL.\n+\n+    Windows ACP clients can launch ``hermes acp`` inside WSL while still sending\n+    editor workspaces as Windows drive paths such as ``E:\\\\Projects``. Store\n+    and execute against the WSL mount path so agents, tools, and persisted ACP\n+    sessions all agree on the usable workspace. Native Linux/macOS keeps the\n+    original cwd unchanged.\n+    \"\"\"\n+    from hermes_constants import is_wsl\n+\n+    if not is_wsl():\n+        return cwd\n+    translated = _win_path_to_wsl(str(cwd))\n+    return translated if translated is not None else cwd\n+\n+\n def _normalize_cwd_for_compare(cwd: str | None) -> str:\n     raw = str(cwd or \".\").strip()\n     if not raw:","@@ -34,11 +61,9 @@ def _normalize_cwd_for_compare(cwd: str | None) -> str:\n \n     # Normalize Windows drive paths into the equivalent WSL mount form so\n     # ACP history filters match the same workspace across Windows and WSL.\n-    match = re.match(r\"^([A-Za-z]):[\\\\/](.*)$\", expanded)\n-    if match:\n-        drive = match.group(1).lower()\n-        tail = match.group(2).replace(\"\\\\\", \"/\")\n-        expanded = f\"/mnt/{drive}/{tail}\"\n+    translated = _win_path_to_wsl(expanded)\n+    if translated is not None:\n+        expanded = translated\n     elif re.match(r\"^/mnt/[A-Za-z]/\", expanded):\n         expanded = f\"/mnt/{expanded[5].lower()}/{expanded[7:]}\"\n ","@@ -96,12 +121,18 @@ def _acp_stderr_print(*args, **kwargs) -> None:\n \n \n def _register_task_cwd(task_id: str, cwd: str) -> None:\n-    \"\"\"Bind a task/session id to the editor's working directory for tools.\"\"\"\n+    \"\"\"Bind a task/session id to the editor's working directory for tools.\n+\n+    Zed can launch Hermes from a Windows workspace while the ACP process runs\n+    inside WSL. In that case ACP sends cwd as e.g. ``E:\\\\Projects\\\\POTI``;\n+    local tools need the WSL mount equivalent or subprocess creation fails\n+    before the command can run.\n+    \"\"\"\n     if not task_id:\n         return\n     try:\n         from tools.terminal_tool import register_task_env_overrides\n-        register_task_env_overrides(task_id, {\"cwd\": cwd})\n+        register_task_env_overrides(task_id, {\"cwd\": _translate_acp_cwd(cwd)})\n     except Exception:\n         logger.debug(\"Failed to register ACP task cwd override\", exc_info=True)\n ","@@ -175,6 +206,7 @@ def create_session(self, cwd: str = \".\") -> SessionState:\n         \"\"\"Create a new session with a unique ID and a fresh AIAgent.\"\"\"\n         import threading\n \n+        cwd = _translate_acp_cwd(cwd)\n         session_id = str(uuid.uuid4())\n         agent = self._make_agent(session_id=session_id, cwd=cwd)\n         state = SessionState(","@@ -217,6 +249,7 @@ def fork_session(self, session_id: str, cwd: str = \".\") -> Optional[SessionState\n         \"\"\"Deep-copy a session's history into a new session.\"\"\"\n         import threading\n \n+        cwd = _translate_acp_cwd(cwd)\n         original = self.get_session(session_id)  # checks DB too\n         if original is None:\n             return None","@@ -318,6 +351,7 @@ def list_sessions(self, cwd: str | None = None) -> List[Dict[str, Any]]:\n \n     def update_cwd(self, session_id: str, cwd: str) -> Optional[SessionState]:\n         \"\"\"Update the working directory for a session and its tool overrides.\"\"\"\n+        cwd = _translate_acp_cwd(cwd)\n         state = self.get_session(session_id)  # checks DB too\n         if state is None:\n             return None"]},{"file":"tests/acp/test_session.py","hunks":["@@ -8,6 +8,7 @@\n import pytest\n from unittest.mock import MagicMock, patch\n \n+from acp_adapter import session as acp_session\n from acp_adapter.session import SessionManager, SessionState\n from hermes_state import SessionDB\n ","@@ -42,6 +43,27 @@ def test_create_session_registers_task_cwd(self, manager, monkeypatch):\n         state = manager.create_session(cwd=\"/tmp/work\")\n         assert calls == [(state.session_id, \"/tmp/work\")]\n \n+\n+    def test_register_task_cwd_translates_windows_drive_for_wsl_tools(self, monkeypatch):\n+        captured = {}\n+\n+        def fake_register_task_env_overrides(task_id, overrides):\n+            captured[\"task_id\"] = task_id\n+            captured[\"overrides\"] = overrides\n+\n+        monkeypatch.setattr(\"hermes_constants._wsl_detected\", True)\n+        monkeypatch.setattr(\n+            \"tools.terminal_tool.register_task_env_overrides\",\n+            fake_register_task_env_overrides,\n+        )\n+\n+        acp_session._register_task_cwd(\"session-1\", r\"E:\\Projects\\AI\\paperclip\")\n+\n+        assert captured == {\n+            \"task_id\": \"session-1\",\n+            \"overrides\": {\"cwd\": \"/mnt/e/Projects/AI/paperclip\"},\n+        }\n+\n     def test_session_ids_are_unique(self, manager):\n         s1 = manager.create_session()\n         s2 = manager.create_session()","@@ -56,6 +78,59 @@ def test_get_nonexistent_session_returns_none(self, manager):\n         assert manager.get_session(\"does-not-exist\") is None\n \n \n+\n+\n+# ---------------------------------------------------------------------------\n+# WSL cwd translation\n+# ---------------------------------------------------------------------------\n+\n+\n+class TestWslCwdTranslation:\n+    def test_translate_acp_cwd_converts_windows_drive_path_when_wsl(self, monkeypatch):\n+        monkeypatch.setattr(\"hermes_constants._wsl_detected\", True)\n+\n+        assert acp_session._translate_acp_cwd(r\"E:\\Projects\\AI\\paperclip\") == \"/mnt/e/Projects/AI/paperclip\"\n+\n+    def test_translate_acp_cwd_handles_forward_slashes_when_wsl(self, monkeypatch):\n+        monkeypatch.setattr(\"hermes_constants._wsl_detected\", True)\n+\n+        assert acp_session._translate_acp_cwd(\"D:/work/project\") == \"/mnt/d/work/project\"\n+\n+    def test_translate_acp_cwd_leaves_windows_drive_path_unchanged_off_wsl(self, monkeypatch):\n+        monkeypatch.setattr(\"hermes_constants._wsl_detected\", False)\n+\n+        assert acp_session._translate_acp_cwd(r\"E:\\Projects\\AI\\paperclip\") == r\"E:\\Projects\\AI\\paperclip\"\n+\n+    def test_translate_acp_cwd_leaves_posix_path_unchanged_on_wsl(self, monkeypatch):\n+        monkeypatch.setattr(\"hermes_constants._wsl_detected\", True)\n+\n+        assert acp_session._translate_acp_cwd(\"/mnt/e/Projects/AI/paperclip\") == \"/mnt/e/Projects/AI/paperclip\"\n+\n+    def test_create_session_stores_translated_cwd_on_wsl(self, manager, monkeypatch):\n+        monkeypatch.setattr(\"hermes_constants._wsl_detected\", True)\n+\n+        state = manager.create_session(cwd=r\"E:\\Projects\\AI\\paperclip\")\n+\n+        assert state.cwd == \"/mnt/e/Projects/AI/paperclip\"\n+\n+    def test_fork_session_stores_translated_cwd_on_wsl(self, manager, monkeypatch):\n+        monkeypatch.setattr(\"hermes_constants._wsl_detected\", True)\n+        original = manager.create_session(cwd=\"/tmp/base\")\n+\n+        forked = manager.fork_session(original.session_id, cwd=r\"D:\\work\\project\")\n+\n+        assert forked is not None\n+        assert forked.cwd == \"/mnt/d/work/project\"\n+\n+    def test_update_cwd_stores_translated_cwd_on_wsl(self, manager, monkeypatch):\n+        monkeypatch.setattr(\"hermes_constants._wsl_detected\", True)\n+        state = manager.create_session(cwd=\"/tmp/old\")\n+\n+        updated = manager.update_cwd(state.session_id, cwd=r\"C:\\Users\\foo\\project\")\n+\n+        assert updated is not None\n+        assert updated.cwd == \"/mnt/c/Users/foo/project\"\n+\n # ---------------------------------------------------------------------------\n # fork\n # ---------------------------------------------------------------------------\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}