From 06e73dde642d8954f40596824e565b14dbb8a42a Mon Sep 17 00:00:00 2001 From: Erosika Date: Fri, 4 Sep 2026 15:37:02 -0400 Subject: [PATCH] fix(honcho): save_config refuses to rewrite a honcho.json that does not parse hermes memory setup writes through HonchoMemoryProvider.save_config, which merged the new values over {} when the existing file could not be parsed and then replaced the file. same wipe as the oauth and cli write paths, through a different door. it now reads through _read_config_strict and raises, so the caller sees the error and the file stays as it was. --- plugins/memory/honcho/__init__.py | 9 ++++++--- tests/test_honcho_client_config.py | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/plugins/memory/honcho/__init__.py b/plugins/memory/honcho/__init__.py index 261018b7c3..af33ab835c 100644 --- a/plugins/memory/honcho/__init__.py +++ b/plugins/memory/honcho/__init__.py @@ -166,11 +166,14 @@ class HonchoMemoryProvider(DialecticMixin, MemoryProvider): return False def save_config(self, values, hermes_home): - """Merge ``values`` into $HERMES_HOME/honcho.json (Honcho SDK native format).""" + """Merge ``values`` into $HERMES_HOME/honcho.json (Honcho SDK native format). + A file that exists but does not parse raises instead of being replaced by ``values`` alone.""" from pathlib import Path - from utils import atomic_json_write, read_json_or_empty + from utils import atomic_json_write + from plugins.memory.honcho.oauth import _read_config_strict config_path = Path(hermes_home) / "honcho.json" - atomic_json_write(config_path, {**read_json_or_empty(config_path), **values}, mode=0o600) + existing = _read_config_strict(config_path) + atomic_json_write(config_path, {**existing, **values}, mode=0o600) def get_config_schema(self): return [ diff --git a/tests/test_honcho_client_config.py b/tests/test_honcho_client_config.py index b23ac21974..f186280d88 100644 --- a/tests/test_honcho_client_config.py +++ b/tests/test_honcho_client_config.py @@ -204,3 +204,26 @@ class TestProfileKeyIsolationWarning: host='hermes', config_path=config_path, ) assert not any('NOT inherited' in r.message for r in caplog.records) + + +def test_save_config_refuses_to_replace_a_file_that_does_not_parse(tmp_path, monkeypatch): + """A corrupt honcho.json must not be rewritten from the new values alone.""" + import pytest + from plugins.memory.honcho import HonchoMemoryProvider + + config_path = tmp_path / "honcho.json" + config_path.write_text("{ not json") + provider = HonchoMemoryProvider() + with pytest.raises(ValueError): + provider.save_config({"api_key": "hc-test-key"}, str(tmp_path)) + assert config_path.read_text() == "{ not json" + + +def test_save_config_merges_into_a_parseable_file(tmp_path): + from plugins.memory.honcho import HonchoMemoryProvider + + config_path = tmp_path / "honcho.json" + config_path.write_text(json.dumps({"hosts": {"other": {"apiKey": "keep-me"}}})) + HonchoMemoryProvider().save_config({"api_key": "hc-test-key"}, str(tmp_path)) + data = json.loads(config_path.read_text()) + assert data["hosts"]["other"]["apiKey"] == "keep-me" and data["api_key"] == "hc-test-key"