mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
fix(config): v32 migration flips baked-in verify_on_stop=true to false (#54740)
The first ship of verify-on-stop (config v30) defaulted DEFAULT_CONFIG agent.verify_on_stop to a literal True, and migrate_config persists defaults with strip_defaults=False — so every install that updated through v30 had verify_on_stop: true written into config.yaml as a literal. The v30->v31 migration only flipped missing/'auto' values to false and deliberately preserved an explicit bool, so it skipped that entire population and left verify-on-stop ON for everyone who had updated. A literal true was never a user choice: the feature had no off-switch worth setting it against until v31 introduced one, so a true persisted before v32 is always the old machine default. v32 migration flips a literal true -> false once, for both v30 (skipped v31) and v31 (preserved-by-bug) installs. A true the user sets AFTER v32 is a deliberate opt-in and is never touched.
This commit is contained in:
parent
75317d82d0
commit
bf0d8fed8e
2 changed files with 59 additions and 2 deletions
|
|
@ -3013,7 +3013,7 @@ DEFAULT_CONFIG = {
|
|||
|
||||
|
||||
# Config schema version - bump this when adding new required fields
|
||||
"_config_version": 31,
|
||||
"_config_version": 32,
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
|
|
@ -5462,6 +5462,34 @@ def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, A
|
|||
"surface-aware behavior."
|
||||
)
|
||||
|
||||
# ── Version 31 → 32: flip the BAKED-IN literal true to OFF (one-time) ──
|
||||
# The v30→v31 flip above only caught missing/"auto" values. But the very
|
||||
# first ship of verify-on-stop (config v30, commit 2f1a47b90) defaulted
|
||||
# DEFAULT_CONFIG["agent"]["verify_on_stop"] to a literal True, and
|
||||
# migrate_config persists defaults with strip_defaults=False — so every
|
||||
# install that updated through v30 got `verify_on_stop: true` written into
|
||||
# config.yaml as a literal. v31's guard deliberately preserves an explicit
|
||||
# bool, so it skipped that whole population and left them ON. That literal
|
||||
# true was never a user choice: the feature had no off-switch worth setting
|
||||
# it against until v31 introduced one, so a true persisted before v32 is
|
||||
# always the old machine default. Flip it off once here. A true the user
|
||||
# sets AFTER v32 (config already at version 32) is never touched.
|
||||
if current_ver < 32:
|
||||
config = read_raw_config()
|
||||
raw_agent = config.get("agent")
|
||||
if isinstance(raw_agent, dict) and raw_agent.get("verify_on_stop") is True:
|
||||
raw_agent["verify_on_stop"] = False
|
||||
config["agent"] = raw_agent
|
||||
save_config(config, strip_defaults=False)
|
||||
results["config_added"].append("agent.verify_on_stop=false")
|
||||
if not quiet:
|
||||
print(
|
||||
" ✓ Turned off verify-on-stop (agent.verify_on_stop: false) — "
|
||||
"the old default was written into your config as a literal "
|
||||
"true. Set it to true again to re-enable, or \"auto\" for the "
|
||||
"legacy surface-aware behavior."
|
||||
)
|
||||
|
||||
# ── Post-migration: disable exfiltration-shaped MCP stdio entries ──
|
||||
# Users can hand-edit mcp_servers, and older installs may already contain a
|
||||
# malicious entry. Preserve the stanza for auditability but mark it
|
||||
|
|
|
|||
|
|
@ -1365,11 +1365,40 @@ class TestVerifyOnStopMigration:
|
|||
raw = yaml.safe_load((tmp_path / "config.yaml").read_text())
|
||||
assert raw["agent"]["verify_on_stop"] is False
|
||||
|
||||
def test_explicit_true_preserved(self, tmp_path):
|
||||
def test_pre_v32_literal_true_flipped_to_false(self, tmp_path):
|
||||
# The first ship of verify-on-stop baked a literal `true` into configs
|
||||
# as the silent default (config v30). It was never a user choice, so the
|
||||
# v31→v32 migration flips it off. v31's block preserved it (the bug this
|
||||
# fixes); v32 catches the whole stranded population.
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path)}):
|
||||
self._write(tmp_path, "_config_version: 30\nagent:\n verify_on_stop: true\n")
|
||||
migrate_config(interactive=False, quiet=True)
|
||||
raw = yaml.safe_load((tmp_path / "config.yaml").read_text())
|
||||
assert raw["agent"]["verify_on_stop"] is False
|
||||
|
||||
def test_v31_literal_true_flipped_to_false(self, tmp_path):
|
||||
# Teknium's case: a v30 install that already ran the v31 migration kept
|
||||
# its baked-in literal `true` (v31 preserved explicit bools). v32 flips
|
||||
# it off.
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path)}):
|
||||
self._write(tmp_path, "_config_version: 31\nagent:\n verify_on_stop: true\n")
|
||||
migrate_config(interactive=False, quiet=True)
|
||||
raw = yaml.safe_load((tmp_path / "config.yaml").read_text())
|
||||
assert raw["agent"]["verify_on_stop"] is False
|
||||
|
||||
def test_post_v32_explicit_true_preserved(self, tmp_path):
|
||||
# A `true` the user sets AFTER v32 (config already at current version) is
|
||||
# a deliberate opt-in and must never be flipped.
|
||||
from hermes_cli.config import DEFAULT_CONFIG
|
||||
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path)}):
|
||||
self._write(
|
||||
tmp_path,
|
||||
f"_config_version: {DEFAULT_CONFIG['_config_version']}\n"
|
||||
"agent:\n verify_on_stop: true\n",
|
||||
)
|
||||
migrate_config(interactive=False, quiet=True)
|
||||
raw = yaml.safe_load((tmp_path / "config.yaml").read_text())
|
||||
assert raw["agent"]["verify_on_stop"] is True
|
||||
|
||||
def test_explicit_false_preserved(self, tmp_path):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue