diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index eb24b9f50eb..997803b8f0a 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -1322,13 +1322,35 @@ def _dashboard_local_update_managed_externally() -> bool: in-browser local update action. Keep this dashboard capability separate from install-method detection: manual git/pip installs inside containers can still behave like their actual install method in the CLI. + + However, when the install method is ``git`` (a bind-mounted checkout inside + a container — e.g. the hermes-webui image sharing the Hermes source tree), + the dashboard's ``hermes update`` button is the correct update path and + should not be suppressed. Other containerized install methods remain + externally managed unless their apply path is proven safe inside the + running container filesystem. """ + if _default_hermes_root_is_opt_data(): + return True try: from hermes_constants import is_container - return is_container() + if not is_container(): + return False except Exception: return False + # We are inside a container, but the install may still be self-managed. + # If the install method is git, the dashboard update button works against + # the mounted checkout and should be offered. Keep pip blocked inside + # containers: its apply path mutates the running container filesystem and + # is not the bind-mounted checkout case this gate is meant to recover. + try: + method = detect_install_method(PROJECT_ROOT) + if method == "git": + return False + except Exception: + pass + return True def _managed_files_policy(request: Request, *, create_root: bool = True) -> ManagedFilesPolicy: diff --git a/scripts/release.py b/scripts/release.py index 7cea21ce9b6..2c781838fc8 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -107,6 +107,7 @@ AUTHOR_MAP = { "804436395@qq.com": "LaPhilosophie", "maxmitcham@mac.home": "maxtrigify", "ccook@nvms.com": "ccook1963", + "libre-7@users.noreply.github.com": "libre-7", "kristian@agrointel.no": "kristianvast", "thomas.paquette@gmail.com": "RyTsYdUp", "techxacm@gmail.com": "ProgramCaiCai", diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py index 0618221a301..76ba0e5f488 100644 --- a/tests/hermes_cli/test_web_server.py +++ b/tests/hermes_cli/test_web_server.py @@ -263,6 +263,29 @@ class TestWebServerEndpoints: import hermes_cli.web_server as web_server monkeypatch.setattr(hermes_constants, "is_container", lambda: True) + # A docker install inside a container should be managed externally. + monkeypatch.setattr(web_server, "detect_install_method", lambda _root: "docker") + + assert web_server._dashboard_local_update_managed_externally() is True + + def test_dashboard_update_capability_allows_git_in_container(self, monkeypatch): + """A git checkout inside a container (e.g. bind-mounted in hermes-webui) + should still offer dashboard updates — the checkout is self-managed.""" + import hermes_constants + import hermes_cli.web_server as web_server + + monkeypatch.setattr(hermes_constants, "is_container", lambda: True) + monkeypatch.setattr(web_server, "detect_install_method", lambda _root: "git") + + assert web_server._dashboard_local_update_managed_externally() is False + + def test_dashboard_update_capability_blocks_pip_in_container(self, monkeypatch): + """A pip install inside a container is still managed externally.""" + import hermes_constants + import hermes_cli.web_server as web_server + + monkeypatch.setattr(hermes_constants, "is_container", lambda: True) + monkeypatch.setattr(web_server, "detect_install_method", lambda _root: "pip") assert web_server._dashboard_local_update_managed_externally() is True @@ -1011,6 +1034,8 @@ class TestWebServerEndpoints: spawned = True raise AssertionError("docker update guard should not spawn hermes update") + # Bypass the managed-externally gate so we reach the docker install check. + monkeypatch.setattr(web_server, "_dashboard_local_update_managed_externally", lambda: False) monkeypatch.setattr(web_server, "detect_install_method", lambda _root: "docker") monkeypatch.setattr(web_server, "_spawn_hermes_action", fail_spawn) web_server._ACTION_PROCS.pop("hermes-update", None)