fix: enforce config.yaml as sole CWD source + deprecate .env CWD vars + add hermes memory reset (#11029)

config.yaml terminal.cwd is now the single source of truth for working
directory. MESSAGING_CWD and TERMINAL_CWD in .env are deprecated with a
migration warning.

Changes:

1. config.py: Remove MESSAGING_CWD from OPTIONAL_ENV_VARS (setup wizard
   no longer prompts for it). Add warn_deprecated_cwd_env_vars() that
   prints a migration hint when deprecated env vars are detected.

2. gateway/run.py: Replace all MESSAGING_CWD reads with TERMINAL_CWD
   (which is bridged from config.yaml terminal.cwd). MESSAGING_CWD is
   still accepted as a backward-compat fallback with deprecation warning.
   Config bridge skips cwd placeholder values so they don't clobber
   the resolved TERMINAL_CWD.

3. cli.py: Guard against lazy-import clobbering — when cli.py is
   imported lazily during gateway runtime (via delegate_tool), don't
   let load_cli_config() overwrite an already-resolved TERMINAL_CWD
   with os.getcwd() of the service's working directory. (#10817)

4. hermes_cli/main.py: Add 'hermes memory reset' command with
   --target all/memory/user and --yes flags. Profile-scoped via
   HERMES_HOME.

Migration path for users with .env settings:
  Remove MESSAGING_CWD / TERMINAL_CWD from .env
  Add to config.yaml:
    terminal:
      cwd: /your/project/path

Addresses: #10225, #4672, #10817, #7663
This commit is contained in:
Teknium
2026-04-16 06:48:33 -07:00
committed by GitHub
parent fe12042e50
commit 3c42064efc
8 changed files with 526 additions and 20 deletions

View File

@@ -37,6 +37,10 @@ def _simulate_config_bridge(cfg: dict, initial_env: dict | None = None):
for cfg_key, env_var in terminal_env_map.items():
if cfg_key in terminal_cfg:
val = terminal_cfg[cfg_key]
# Skip cwd placeholder values — don't overwrite already-resolved
# TERMINAL_CWD. Mirrors the fix in gateway/run.py.
if cfg_key == "cwd" and str(val) in (".", "auto", "cwd"):
continue
if isinstance(val, list):
env[env_var] = json.dumps(val)
else:
@@ -146,3 +150,58 @@ class TestTopLevelCwdAlias:
cfg = {"cwd": "/from/config"}
result = _simulate_config_bridge(cfg, {"MESSAGING_CWD": "/from/env"})
assert result["TERMINAL_CWD"] == "/from/config"
class TestNestedTerminalCwdPlaceholderSkip:
"""terminal.cwd placeholder values must not clobber TERMINAL_CWD.
When config.yaml has terminal.cwd: "." (or "auto"/"cwd"), the gateway
config bridge should NOT write that placeholder to TERMINAL_CWD.
This prevents .env or MESSAGING_CWD values from being overwritten.
See issues #10225, #4672, #10817.
"""
def test_terminal_dot_cwd_does_not_clobber_env(self):
"""terminal.cwd: '.' should not overwrite a pre-set TERMINAL_CWD."""
cfg = {"terminal": {"cwd": "."}}
result = _simulate_config_bridge(cfg, {"TERMINAL_CWD": "/my/project"})
assert result["TERMINAL_CWD"] == "/my/project"
def test_terminal_auto_cwd_does_not_clobber_env(self):
cfg = {"terminal": {"cwd": "auto"}}
result = _simulate_config_bridge(cfg, {"TERMINAL_CWD": "/my/project"})
assert result["TERMINAL_CWD"] == "/my/project"
def test_terminal_cwd_keyword_does_not_clobber_env(self):
cfg = {"terminal": {"cwd": "cwd"}}
result = _simulate_config_bridge(cfg, {"TERMINAL_CWD": "/my/project"})
assert result["TERMINAL_CWD"] == "/my/project"
def test_terminal_explicit_cwd_does_override(self):
"""terminal.cwd: '/explicit/path' SHOULD override TERMINAL_CWD."""
cfg = {"terminal": {"cwd": "/explicit/path"}}
result = _simulate_config_bridge(cfg, {"TERMINAL_CWD": "/old/value"})
assert result["TERMINAL_CWD"] == "/explicit/path"
def test_terminal_dot_cwd_falls_back_to_messaging_cwd(self):
"""terminal.cwd: '.' with no TERMINAL_CWD should fall to MESSAGING_CWD."""
cfg = {"terminal": {"cwd": "."}}
result = _simulate_config_bridge(cfg, {"MESSAGING_CWD": "/from/env"})
assert result["TERMINAL_CWD"] == "/from/env"
def test_terminal_dot_cwd_and_messaging_cwd_both_set(self):
"""Pre-set TERMINAL_CWD from .env wins over terminal.cwd: '.'."""
cfg = {"terminal": {"cwd": ".", "backend": "local"}}
result = _simulate_config_bridge(cfg, {
"TERMINAL_CWD": "/my/project",
"MESSAGING_CWD": "/fallback",
})
assert result["TERMINAL_CWD"] == "/my/project"
def test_non_cwd_terminal_keys_still_bridge(self):
"""Other terminal config keys (backend, timeout) should still bridge normally."""
cfg = {"terminal": {"cwd": ".", "backend": "docker", "timeout": "300"}}
result = _simulate_config_bridge(cfg, {"MESSAGING_CWD": "/from/env"})
assert result["TERMINAL_ENV"] == "docker"
assert result["TERMINAL_TIMEOUT"] == "300"
assert result["TERMINAL_CWD"] == "/from/env"