mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-01 01:51:44 +00:00
init_session() runs a login shell bootstrap that sources profile scripts (.bashrc, .bash_profile, etc.) before capturing pwd. If any profile script changes the working directory, the captured cwd overwrites the configured terminal.cwd value — so terminal commands run in the wrong directory despite the TUI banner showing the configured path. Add an explicit 'builtin cd' to the configured cwd in the bootstrap script, after profile sourcing but before pwd capture, ensuring the configured terminal.cwd is always what gets recorded. Fixes #14044
148 lines
5.3 KiB
Python
148 lines
5.3 KiB
Python
"""Tests that init_session() respects the configured cwd.
|
|
|
|
The bug: when terminal.cwd is set in config.yaml, the configured path was
|
|
displayed in the TUI banner but actual terminal commands ran in os.getcwd()
|
|
(the directory where ``hermes chat`` was started).
|
|
|
|
Root cause: init_session() captures the login shell environment by running
|
|
``pwd -P`` inside a ``bash -l -c`` bootstrap. Profile scripts (.bashrc,
|
|
.bash_profile, etc.) can change the working directory before ``pwd -P``
|
|
runs, so _update_cwd() overwrites self.cwd with the wrong directory.
|
|
|
|
Fix: the bootstrap now includes an explicit ``cd`` back to self.cwd before
|
|
running ``pwd -P``, so the configured cwd is always what gets recorded.
|
|
"""
|
|
|
|
from tempfile import TemporaryFile
|
|
from unittest.mock import MagicMock
|
|
|
|
from tools.environments.base import BaseEnvironment
|
|
|
|
|
|
class _TestableEnv(BaseEnvironment):
|
|
"""Concrete subclass for testing base class methods."""
|
|
|
|
def __init__(self, cwd="/tmp", timeout=10):
|
|
super().__init__(cwd=cwd, timeout=timeout)
|
|
|
|
def _run_bash(self, cmd_string, *, login=False, timeout=120, stdin_data=None):
|
|
raise NotImplementedError("Use mock")
|
|
|
|
def cleanup(self):
|
|
pass
|
|
|
|
|
|
class TestInitSessionCwdRespect:
|
|
"""init_session() must preserve the configured cwd."""
|
|
|
|
def test_bootstrap_contains_cd_to_configured_cwd(self):
|
|
"""The bootstrap script must cd to self.cwd before running pwd."""
|
|
env = _TestableEnv(cwd="/my/project")
|
|
|
|
# Capture the bootstrap script that init_session would pass to _run_bash
|
|
captured = {}
|
|
|
|
def mock_run_bash(cmd_string, *, login=False, timeout=120, stdin_data=None):
|
|
captured["cmd"] = cmd_string
|
|
mock = MagicMock()
|
|
mock.poll.return_value = 0
|
|
mock.returncode = 0
|
|
stdout = TemporaryFile(mode="w+b")
|
|
stdout.seek(0)
|
|
mock.stdout = stdout
|
|
return mock
|
|
|
|
env._run_bash = mock_run_bash
|
|
env.init_session()
|
|
|
|
assert "cmd" in captured, "init_session did not call _run_bash"
|
|
bootstrap = captured["cmd"]
|
|
|
|
# The cd must appear before pwd -P so the configured cwd is recorded
|
|
cd_pos = bootstrap.find("builtin cd")
|
|
pwd_pos = bootstrap.find("pwd -P")
|
|
assert cd_pos != -1, "bootstrap must contain 'builtin cd'"
|
|
assert pwd_pos != -1, "bootstrap must contain 'pwd -P'"
|
|
assert cd_pos < pwd_pos, (
|
|
"builtin cd must appear before pwd -P in the bootstrap so "
|
|
"the configured cwd is what gets recorded"
|
|
)
|
|
|
|
# The cd target must be the configured path (shlex.quote only adds
|
|
# quotes when the path contains shell-special characters)
|
|
assert "/my/project" in bootstrap, (
|
|
"bootstrap cd must target the configured cwd (/my/project)"
|
|
)
|
|
|
|
def test_configured_cwd_survives_init_session(self):
|
|
"""self.cwd must be the configured path after init_session completes."""
|
|
configured_cwd = "/my/project"
|
|
env = _TestableEnv(cwd=configured_cwd)
|
|
|
|
marker = env._cwd_marker
|
|
|
|
def mock_run_bash(cmd_string, *, login=False, timeout=120, stdin_data=None):
|
|
mock = MagicMock()
|
|
mock.poll.return_value = 0
|
|
mock.returncode = 0
|
|
# Simulate output where pwd reports the configured cwd
|
|
output = f"snapshot output\n{marker}{configured_cwd}{marker}\n"
|
|
stdout = TemporaryFile(mode="w+b")
|
|
stdout.write(output.encode("utf-8"))
|
|
stdout.seek(0)
|
|
mock.stdout = stdout
|
|
return mock
|
|
|
|
env._run_bash = mock_run_bash
|
|
env.init_session()
|
|
|
|
assert env.cwd == configured_cwd, (
|
|
f"Expected cwd={configured_cwd!r} after init_session, got {env.cwd!r}"
|
|
)
|
|
|
|
def test_default_cwd_still_works(self):
|
|
"""When no custom cwd is configured, default /tmp behavior is preserved."""
|
|
env = _TestableEnv() # default cwd="/tmp"
|
|
|
|
marker = env._cwd_marker
|
|
|
|
def mock_run_bash(cmd_string, *, login=False, timeout=120, stdin_data=None):
|
|
mock = MagicMock()
|
|
mock.poll.return_value = 0
|
|
mock.returncode = 0
|
|
output = f"snapshot output\n{marker}/tmp{marker}\n"
|
|
stdout = TemporaryFile(mode="w+b")
|
|
stdout.write(output.encode("utf-8"))
|
|
stdout.seek(0)
|
|
mock.stdout = stdout
|
|
return mock
|
|
|
|
env._run_bash = mock_run_bash
|
|
env.init_session()
|
|
|
|
assert env.cwd == "/tmp"
|
|
|
|
def test_bootstrap_cd_uses_shlex_quote(self):
|
|
"""Paths with spaces must be properly quoted in the bootstrap cd."""
|
|
env = _TestableEnv(cwd="/my project/with spaces")
|
|
|
|
captured = {}
|
|
|
|
def mock_run_bash(cmd_string, *, login=False, timeout=120, stdin_data=None):
|
|
captured["cmd"] = cmd_string
|
|
mock = MagicMock()
|
|
mock.poll.return_value = 0
|
|
mock.returncode = 0
|
|
stdout = TemporaryFile(mode="w+b")
|
|
stdout.seek(0)
|
|
mock.stdout = stdout
|
|
return mock
|
|
|
|
env._run_bash = mock_run_bash
|
|
env.init_session()
|
|
|
|
bootstrap = captured["cmd"]
|
|
# shlex.quote wraps paths with spaces in single quotes
|
|
assert "'/my project/with spaces'" in bootstrap, (
|
|
"bootstrap cd must properly quote paths with spaces"
|
|
)
|