diff --git a/README.md b/README.md index 72982f29..e5348d96 100644 --- a/README.md +++ b/README.md @@ -337,7 +337,7 @@ Full list of environment variables: | `HERMES_WEBUI_PYTHON` | auto-discovered | Python executable | | `HERMES_WEBUI_HOST` | `127.0.0.1` | Bind address (`0.0.0.0` for all IPv4, `::` for all IPv6, `::1` for IPv6 loopback) | | `HERMES_WEBUI_PORT` | `8787` | Port | -| `HERMES_WEBUI_STATE_DIR` | `$HERMES_HOME/webui` (Windows default `%LOCALAPPDATA%\hermes\webui`, POSIX default `~/.hermes/webui`) | Where sessions and state are stored | +| `HERMES_WEBUI_STATE_DIR` | `$HERMES_HOME/webui` (Windows default `%LOCALAPPDATA%\hermes\webui`, POSIX default `~/.hermes/webui`) | Where sessions and state are stored. **Note (upgrade):** the default now follows `HERMES_HOME` — if you previously relocated `HERMES_HOME` to a non-default base **without** setting `HERMES_WEBUI_STATE_DIR`, your WebUI state now resolves to `$HERMES_HOME/webui` instead of the old platform-default `~/.hermes/webui`. To keep using the old location, set `HERMES_WEBUI_STATE_DIR` to it (or move the directory). Installs with `HERMES_HOME` unset or at the default base are unaffected. | | `HERMES_WEBUI_DEFAULT_WORKSPACE` | `~/workspace` | Default workspace | | `HERMES_WEBUI_DEFAULT_MODEL` | *(provider default)* | Optional model override; leave unset to use the active Hermes provider default | | `HERMES_WEBUI_PASSWORD` | *(unset)* | Set to enable password authentication | diff --git a/tests/test_issue4449_state_dir_contract.py b/tests/test_issue4449_state_dir_contract.py index 81ae37c8..43922851 100644 --- a/tests/test_issue4449_state_dir_contract.py +++ b/tests/test_issue4449_state_dir_contract.py @@ -31,3 +31,59 @@ def test_config_state_dir_defaults_to_hermes_home_webui(tmp_path): else: os.environ["HERMES_WEBUI_STATE_DIR"] = old_state_dir importlib.reload(config) + + +def test_config_state_dir_unchanged_for_normal_install_hermes_home_unset(tmp_path): + """Backward-compat: with HERMES_HOME unset, STATE_DIR stays at the platform + default `<~/.hermes>/webui` — a normal install's state must NOT relocate + (the #4449/#4454 state-dir move only affects an explicitly-set HERMES_HOME).""" + old_home = os.environ.get("HERMES_HOME") + old_state_dir = os.environ.get("HERMES_WEBUI_STATE_DIR") + try: + os.environ.pop("HERMES_HOME", None) + os.environ.pop("HERMES_WEBUI_STATE_DIR", None) + + reloaded = importlib.reload(config) + + # _platform_default_hermes_home() drives the default; STATE_DIR must be + # that base + /webui, exactly as on master before the #4449 change. + expected = (reloaded._platform_default_hermes_home() / "webui").resolve() + assert reloaded.STATE_DIR == expected + finally: + if old_home is None: + os.environ.pop("HERMES_HOME", None) + else: + os.environ["HERMES_HOME"] = old_home + if old_state_dir is None: + os.environ.pop("HERMES_WEBUI_STATE_DIR", None) + else: + os.environ["HERMES_WEBUI_STATE_DIR"] = old_state_dir + importlib.reload(config) + + +def test_config_state_dir_explicit_override_takes_precedence(tmp_path): + """HERMES_WEBUI_STATE_DIR always wins over the HERMES_HOME-derived default, + so an operator who pinned a state dir keeps it even in isolated mode.""" + hermes_home = tmp_path / ".hermes" / "profiles" / "isolated" + hermes_home.mkdir(parents=True) + explicit = tmp_path / "custom-state" + + old_home = os.environ.get("HERMES_HOME") + old_state_dir = os.environ.get("HERMES_WEBUI_STATE_DIR") + try: + os.environ["HERMES_HOME"] = str(hermes_home) + os.environ["HERMES_WEBUI_STATE_DIR"] = str(explicit) + + reloaded = importlib.reload(config) + + assert reloaded.STATE_DIR == explicit.resolve() + finally: + if old_home is None: + os.environ.pop("HERMES_HOME", None) + else: + os.environ["HERMES_HOME"] = old_home + if old_state_dir is None: + os.environ.pop("HERMES_WEBUI_STATE_DIR", None) + else: + os.environ["HERMES_WEBUI_STATE_DIR"] = old_state_dir + importlib.reload(config)