fix(api): allow dashboard updates for git checkouts in containers (#51005)

Salvages #50469 by @libre-7.

_dashboard_local_update_managed_externally() previously blocked every containerized dashboard from the local update API, even when the running install was a bind-mounted git checkout that can be updated with hermes update.

Allow the dashboard updater only for git installs inside containers, while keeping hosted /opt/data, docker, and pip installs managed externally. Pip remains blocked because its apply path mutates the running container filesystem and is not the self-managed checkout case.

Adds regression coverage for docker, git, and pip install-method handling inside containers, and maps the contributor email for release attribution.

Co-authored-by: libre-7 <libre-7@users.noreply.github.com>
This commit is contained in:
Austin Pickett 2026-06-22 15:55:33 -04:00 committed by GitHub
parent 6681f28d5b
commit 2a58fee1a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 49 additions and 1 deletions

View file

@ -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:

View file

@ -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",

View file

@ -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)