From 27ec74c68a16d411f1184dfae45d139dda33d6d5 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:39:29 -0700 Subject: [PATCH] fix: coerce show_reasoning and guard_agent_created config bools Widens #16528 to two sibling sites that had the same quoted-boolean bug: a YAML string "false" (or "0", "no", "off") silently evaluated truthy under bool() / if-check. - gateway/run.py _load_show_reasoning: is_truthy_value wrap - tools/skill_manager_tool.py _guard_agent_created_enabled: is_truthy_value wrap - regression tests for both --- gateway/run.py | 5 ++- tests/gateway/test_reasoning_command.py | 41 +++++++++++++++++++++++++ tests/tools/test_skill_manager_tool.py | 20 ++++++++++++ tools/skill_manager_tool.py | 7 +++-- 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/gateway/run.py b/gateway/run.py index 144bbe41d1..8c2c6478cb 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -1742,7 +1742,10 @@ class GatewayRunner: if cfg_path.exists(): with open(cfg_path, encoding="utf-8") as _f: cfg = _y.safe_load(_f) or {} - return bool(cfg_get(cfg, "display", "show_reasoning", default=False)) + return is_truthy_value( + cfg_get(cfg, "display", "show_reasoning"), + default=False, + ) except Exception: pass return False diff --git a/tests/gateway/test_reasoning_command.py b/tests/gateway/test_reasoning_command.py index 5020df30a7..f22704dedf 100644 --- a/tests/gateway/test_reasoning_command.py +++ b/tests/gateway/test_reasoning_command.py @@ -407,3 +407,44 @@ class TestReasoningCommand: assert result["final_response"] == "ok" assert _CapturingAgent.last_init is not None assert "homeassistant" in set(_CapturingAgent.last_init["enabled_toolsets"]) + + +class TestLoadShowReasoningCoercion: + """Regression: display.show_reasoning must be coerced, not bool()'d.""" + + def _load_with_config(self, tmp_path, monkeypatch, yaml_body: str) -> bool: + hermes_home = tmp_path / "hermes" + hermes_home.mkdir() + (hermes_home / "config.yaml").write_text(yaml_body, encoding="utf-8") + monkeypatch.setattr(gateway_run, "_hermes_home", hermes_home) + return gateway_run.GatewayRunner._load_show_reasoning() + + def test_quoted_false_is_false(self, tmp_path, monkeypatch): + assert self._load_with_config( + tmp_path, monkeypatch, + 'display:\n show_reasoning: "false"\n', + ) is False + + def test_quoted_off_is_false(self, tmp_path, monkeypatch): + assert self._load_with_config( + tmp_path, monkeypatch, + 'display:\n show_reasoning: "off"\n', + ) is False + + def test_quoted_true_is_true(self, tmp_path, monkeypatch): + assert self._load_with_config( + tmp_path, monkeypatch, + 'display:\n show_reasoning: "true"\n', + ) is True + + def test_bare_true_is_true(self, tmp_path, monkeypatch): + assert self._load_with_config( + tmp_path, monkeypatch, + 'display:\n show_reasoning: true\n', + ) is True + + def test_missing_is_false(self, tmp_path, monkeypatch): + assert self._load_with_config( + tmp_path, monkeypatch, + 'display: {}\n', + ) is False diff --git a/tests/tools/test_skill_manager_tool.py b/tests/tools/test_skill_manager_tool.py index 9fc8957f1e..00eaf51ea0 100644 --- a/tests/tools/test_skill_manager_tool.py +++ b/tests/tools/test_skill_manager_tool.py @@ -567,6 +567,26 @@ class TestSecurityScanGate: with patch("hermes_cli.config.load_config", side_effect=RuntimeError("boom")): assert _guard_agent_created_enabled() is False + def test_guard_flag_quoted_false_stays_disabled(self): + """Quoted 'false' from YAML edits must not enable the guard.""" + from tools.skill_manager_tool import _guard_agent_created_enabled + + for quoted in ("false", "False", "0", "no", "off"): + with patch("hermes_cli.config.load_config", + return_value={"skills": {"guard_agent_created": quoted}}): + assert _guard_agent_created_enabled() is False, \ + f"guard_agent_created={quoted!r} must coerce to False" + + def test_guard_flag_quoted_true_enables(self): + """Quoted truthy strings must enable the guard.""" + from tools.skill_manager_tool import _guard_agent_created_enabled + + for quoted in ("true", "True", "1", "yes", "on"): + with patch("hermes_cli.config.load_config", + return_value={"skills": {"guard_agent_created": quoted}}): + assert _guard_agent_created_enabled() is True, \ + f"guard_agent_created={quoted!r} must coerce to True" + # --------------------------------------------------------------------------- # External skills directories (skills.external_dirs) — mutations in place diff --git a/tools/skill_manager_tool.py b/tools/skill_manager_tool.py index cc8b0fed28..e1b9a5f055 100644 --- a/tools/skill_manager_tool.py +++ b/tools/skill_manager_tool.py @@ -42,7 +42,7 @@ from pathlib import Path from hermes_constants import get_hermes_home, display_hermes_home from typing import Dict, Any, Optional, Tuple -from utils import atomic_replace +from utils import atomic_replace, is_truthy_value from hermes_cli.config import cfg_get logger = logging.getLogger(__name__) @@ -67,7 +67,10 @@ def _guard_agent_created_enabled() -> bool: try: from hermes_cli.config import load_config cfg = load_config() - return bool(cfg_get(cfg, "skills", "guard_agent_created", default=False)) + return is_truthy_value( + cfg_get(cfg, "skills", "guard_agent_created"), + default=False, + ) except Exception: return False