mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-29 18:46:59 +00:00
fix(config): persist runtime settings to HERMES_HOME/config.yaml, not the repo template
The wake-word ear reverted to disabled after every restart even when
closed enabled. Root cause is general, not wake-specific: save_config_value
followed load_cli_config's precedence, which falls back to the repo's
checked-in cli-config.yaml when HERMES_HOME/config.yaml doesn't exist yet.
On such installs (managed/desktop first launch), the toggle's persist wrote
wake_word.enabled=true into cli-config.yaml and returned success — but
every config reader (load_config -> get_hermes_home()/config.yaml,
including load_wake_word_config) reads only HERMES_HOME/config.yaml, so the
setting was invisible on the next launch. Same silent loss hit any runtime
persist (model switch, /reasoning, /fast, skin) on a config-less install.
save_config_value now always targets get_hermes_home()/config.yaml,
creating it if absent, and never writes the shipped repo template. Also
resolves HERMES_HOME live instead of the import-time _hermes_home constant
(profile-safe).
E2E verified: persist -> fresh module reload -> load_wake_word_config sees
enabled=true and wake_surface_enabled('gui') is True, for both a fresh
(no config.yaml) and an existing-config install.
- cli.py save_config_value: target user config, create if absent
- tests: 2 regression tests (creates user config when absent; never writes
repo cli-config.yaml); fixture now sets HERMES_HOME. 11 file + 206
adjacent config/model-switch tests green
This commit is contained in:
parent
ffe5a93479
commit
353578faca
2 changed files with 69 additions and 5 deletions
18
cli.py
18
cli.py
|
|
@ -4020,10 +4020,20 @@ def save_config_value(key_path: str, value: any) -> bool:
|
|||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
# Use the same precedence as load_cli_config: user config first, then project config
|
||||
user_config_path = _hermes_home / 'config.yaml'
|
||||
project_config_path = Path(__file__).parent / 'cli-config.yaml'
|
||||
config_path = user_config_path if user_config_path.exists() else project_config_path
|
||||
# Runtime persistence ALWAYS targets the user's HERMES_HOME config.yaml,
|
||||
# creating it if needed. Resolve HERMES_HOME live (not the import-time
|
||||
# _hermes_home constant) so profile switches and test isolation land right.
|
||||
#
|
||||
# We deliberately do NOT fall back to the repo's project cli-config.yaml:
|
||||
# that file is a shipped default/template, and most config readers
|
||||
# (load_config → get_hermes_home()/config.yaml, including
|
||||
# load_wake_word_config) never read it. Writing a user setting there means
|
||||
# the reader never sees it. This was the "wake-word ear reverts to disabled
|
||||
# after restart" bug — the toggle's persist wrote to cli-config.yaml (which
|
||||
# exists in the checkout) while startup read HERMES_HOME/config.yaml, so the
|
||||
# setting silently vanished every restart on any install whose
|
||||
# HERMES_HOME/config.yaml didn't exist yet.
|
||||
config_path = get_hermes_home() / 'config.yaml'
|
||||
|
||||
try:
|
||||
# Ensure parent directory exists (for ~/.hermes/config.yaml on first use)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
"""Tests for save_config_value() in cli.py — atomic write behavior."""
|
||||
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import yaml
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
@ -19,6 +21,10 @@ class TestSaveConfigValueAtomic:
|
|||
"model": {"default": "test-model", "provider": "openrouter"},
|
||||
"display": {"skin": "default"},
|
||||
}))
|
||||
# save_config_value resolves the target live via get_hermes_home(), so
|
||||
# point HERMES_HOME at the temp dir (the _hermes_home import-time
|
||||
# constant is no longer consulted).
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
monkeypatch.setattr("cli._hermes_home", hermes_home)
|
||||
return config_path
|
||||
|
||||
|
|
@ -144,3 +150,51 @@ class TestSaveConfigValueAtomic:
|
|||
|
||||
assert result is False
|
||||
assert config_env.read_text() == original_content
|
||||
|
||||
|
||||
class TestSaveConfigValueTargetsUserConfig:
|
||||
"""Regression: persisted runtime settings must land in HERMES_HOME/config.yaml
|
||||
(which config readers actually read), never the repo's cli-config.yaml.
|
||||
|
||||
This was the "wake-word ear reverts to disabled after restart" bug: on an
|
||||
install whose HERMES_HOME/config.yaml did not exist yet, save_config_value
|
||||
fell back to the checked-in cli-config.yaml. The toggle reported success, but
|
||||
startup read HERMES_HOME/config.yaml and never saw the setting."""
|
||||
|
||||
def test_creates_user_config_when_absent(self, tmp_path, monkeypatch):
|
||||
# Fresh HERMES_HOME with NO config.yaml (managed/desktop first launch).
|
||||
hermes_home = tmp_path / ".hermes"
|
||||
hermes_home.mkdir()
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
|
||||
from cli import save_config_value
|
||||
|
||||
assert save_config_value("wake_word.enabled", True) is True
|
||||
|
||||
config_path = hermes_home / "config.yaml"
|
||||
assert config_path.exists(), "user config.yaml must be created, not skipped"
|
||||
result = yaml.safe_load(config_path.read_text())
|
||||
assert result["wake_word"]["enabled"] is True
|
||||
|
||||
def test_does_not_write_repo_cli_config(self, tmp_path, monkeypatch):
|
||||
# Even when the repo's cli-config.yaml exists, the write goes to the
|
||||
# user config, so a runtime setting is never buried in the shipped file.
|
||||
import cli as cli_module
|
||||
|
||||
repo_cli_config = Path(cli_module.__file__).parent / "cli-config.yaml"
|
||||
before = repo_cli_config.read_text() if repo_cli_config.exists() else None
|
||||
|
||||
hermes_home = tmp_path / ".hermes"
|
||||
hermes_home.mkdir()
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
|
||||
from cli import save_config_value
|
||||
|
||||
save_config_value("wake_word.enabled", True)
|
||||
|
||||
# The repo template is untouched…
|
||||
after = repo_cli_config.read_text() if repo_cli_config.exists() else None
|
||||
assert after == before
|
||||
# …and the value landed in the user config.
|
||||
result = yaml.safe_load((hermes_home / "config.yaml").read_text())
|
||||
assert result["wake_word"]["enabled"] is True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue