diff --git a/cli.py b/cli.py index 97698f133d08..2964455f9dbd 100644 --- a/cli.py +++ b/cli.py @@ -396,14 +396,26 @@ def load_cli_config() -> Dict[str, Any]: if "backend" in terminal_config: terminal_config["env_type"] = terminal_config["backend"] - # Handle special cwd values: "." or "auto" means use current working directory. - # Only resolve to the host's CWD for the local backend where the host - # filesystem is directly accessible. For ALL remote/container backends - # (ssh, docker, modal, singularity), the host path doesn't exist on the - # target -- remove the key so terminal_tool.py uses its per-backend default. + # Handle special cwd values: "." or "auto" means use a concrete working + # directory. For local backends, prefer an already-established TERMINAL_CWD + # (e.g. set by gateway/run.py from MESSAGING_CWD) so we do not clobber the + # gateway's workspace with os.getcwd(). For remote/container backends, + # remove the key so terminal_tool.py uses its per-backend default. if terminal_config.get("cwd") in (".", "auto", "cwd"): effective_backend = terminal_config.get("env_type", "local") - if effective_backend == "local": + existing_terminal_cwd = os.environ.get("TERMINAL_CWD", "").strip() + if effective_backend == "local" and existing_terminal_cwd: + try: + if Path(existing_terminal_cwd).expanduser().is_dir(): + terminal_config["cwd"] = existing_terminal_cwd + defaults["terminal"]["cwd"] = existing_terminal_cwd + else: + terminal_config["cwd"] = os.getcwd() + defaults["terminal"]["cwd"] = terminal_config["cwd"] + except OSError: + terminal_config["cwd"] = os.getcwd() + defaults["terminal"]["cwd"] = terminal_config["cwd"] + elif effective_backend == "local": terminal_config["cwd"] = os.getcwd() defaults["terminal"]["cwd"] = terminal_config["cwd"] else: diff --git a/tests/cli/test_cli_init.py b/tests/cli/test_cli_init.py index b926d55f535d..b09a20176036 100644 --- a/tests/cli/test_cli_init.py +++ b/tests/cli/test_cli_init.py @@ -331,6 +331,54 @@ def test_normalize_root_model_keys_does_not_override_existing(self): assert "provider" not in result # root key still cleaned up +class TestTerminalCwdPrecedence: + """load_cli_config() should respect an already-set TERMINAL_CWD.""" + + def test_existing_terminal_cwd_wins_over_dot_cwd(self, tmp_path, monkeypatch): + import yaml + import cli + + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + config_path = hermes_home / "config.yaml" + config_path.write_text(yaml.safe_dump({"terminal": {"env_type": "local", "cwd": "."}})) + + workspace = tmp_path / "messaging-workspace" + workspace.mkdir() + system_cwd = tmp_path / "systemd-working-dir" + system_cwd.mkdir() + + monkeypatch.setattr(cli, "_hermes_home", hermes_home) + monkeypatch.setenv("TERMINAL_CWD", str(workspace)) + monkeypatch.chdir(system_cwd) + + cfg = cli.load_cli_config() + + assert cfg["terminal"]["cwd"] == str(workspace) + assert os.environ["TERMINAL_CWD"] == str(workspace) + + def test_missing_terminal_cwd_falls_back_to_os_cwd(self, tmp_path, monkeypatch): + import yaml + import cli + + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + config_path = hermes_home / "config.yaml" + config_path.write_text(yaml.safe_dump({"terminal": {"env_type": "local", "cwd": "."}})) + + system_cwd = tmp_path / "systemd-working-dir" + system_cwd.mkdir() + + monkeypatch.setattr(cli, "_hermes_home", hermes_home) + monkeypatch.delenv("TERMINAL_CWD", raising=False) + monkeypatch.chdir(system_cwd) + + cfg = cli.load_cli_config() + + assert cfg["terminal"]["cwd"] == str(system_cwd) + assert os.environ["TERMINAL_CWD"] == str(system_cwd) + + class TestProviderResolution: def test_api_key_is_string_or_none(self): cli = _make_cli()