From 92a83980878ef568d314e4ac50793bf0c8b2c459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E9=B9=A40668001310?= Date: Mon, 20 Apr 2026 15:10:45 +0800 Subject: [PATCH] fix(config): treat explicit false values in HERMES_MANAGED as unmanaged Previously, setting HERMES_MANAGED=false (or 0, no, off) would be interpreted as a literal managed-system name, causing is_managed() to incorrectly return True and block update/config commands. - Add _MANAGED_FALSE_VALUES tuple for canonical false strings - Check false values before true values in get_managed_system() - Add parametrized regression tests for all false variants Fixes #12864 --- hermes_cli/config.py | 5 ++++- tests/hermes_cli/test_managed_installs.py | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 16ae90b4e4..3d8a8ba143 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -243,6 +243,9 @@ _NIX_STORE = Path("/nix/store") # Homebrew is no longer a supported distribution: these markers fall through to git/unknown # detection instead of blocking config writes. _IGNORED_MANAGED_VALUES = frozenset({"brew", "homebrew"}) +# Explicit opt-out (``HERMES_MANAGED=false``): without this a bool-shaped value became a package +# manager literally named "false" and is_managed() blocked `hermes update` (#12864). +_MANAGED_FALSE_VALUES = frozenset({"false", "0", "no", "off"}) def get_managed_system() -> Optional[str]: @@ -256,7 +259,7 @@ def get_managed_system() -> Optional[str]: marker = managed_marker.read_text(encoding="utf-8", errors="replace").strip().lower() except OSError: marker = "" - if marker is None or marker in _IGNORED_MANAGED_VALUES: + if marker is None or marker in _IGNORED_MANAGED_VALUES or marker in _MANAGED_FALSE_VALUES: return None if marker == "" or marker in _MANAGED_TRUE_VALUES: return _LEGACY_MANAGED_SYSTEM diff --git a/tests/hermes_cli/test_managed_installs.py b/tests/hermes_cli/test_managed_installs.py index 94a01b0cdf..408e682792 100644 --- a/tests/hermes_cli/test_managed_installs.py +++ b/tests/hermes_cli/test_managed_installs.py @@ -1,7 +1,9 @@ from types import SimpleNamespace from unittest.mock import patch -from hermes_cli.config import recommended_update_command +import pytest + +from hermes_cli.config import get_managed_system, is_managed, recommended_update_command from hermes_cli.main import cmd_update from tools.skills_hub_official import OptionalSkillSource @@ -19,6 +21,17 @@ def test_recommended_update_command_defaults_to_hermes_update(monkeypatch): assert recommended_update_command() == "hermes update" +@pytest.mark.parametrize("false_value", ["false", "0", "no", "off", "FALSE"]) +def test_get_managed_system_false_values(monkeypatch, false_value): + """An explicit opt-out is not a package manager named "false" (#12864).""" + monkeypatch.setenv("HERMES_MANAGED", false_value) + + assert get_managed_system() is None + assert not is_managed() + with patch("hermes_cli.config.detect_install_method", return_value="git"): + assert recommended_update_command() == "hermes update" + + def test_optional_skill_source_honors_env_override(monkeypatch, tmp_path): optional_dir = tmp_path / "optional-skills" optional_dir.mkdir()