From 43ec2036ec1d21f8a82dd6c079fedd981daa4ecf Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sat, 12 Sep 2026 23:48:30 -0700 Subject: [PATCH] fix(hindsight): corrupt config.json falls through to the legacy file and env, as before read_json_or_empty returns {} for malformed JSON; returning that directly from _load_config made a corrupt profile config.json yield an empty (silently unconfigured) mapping where the pre-dedup loop kept walking to the legacy file and then the env branch. Only a non-empty parsed object is now returned. --- plugins/memory/hindsight/__init__.py | 5 +++-- tests/plugins/memory/test_hindsight_provider.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/plugins/memory/hindsight/__init__.py b/plugins/memory/hindsight/__init__.py index 1cc6d708bc..6c52994765 100644 --- a/plugins/memory/hindsight/__init__.py +++ b/plugins/memory/hindsight/__init__.py @@ -235,8 +235,9 @@ def _load_config() -> dict: """$HERMES_HOME/hindsight/config.json (profile-scoped), else ~/.hindsight/config.json (legacy, shared), else environment variables.""" for path in (get_hermes_home() / "hindsight" / "config.json", Path.home() / ".hindsight" / "config.json"): - if path.exists(): - return read_json_or_empty(path) + # A corrupt (or empty) file falls through to the next source, as before the dedup. + if path.exists() and (data := read_json_or_empty(path)): + return data # Mode, bank (the data partition), endpoint and retain shaping are per-profile .env values like # the key beside them: read through the secret scope so a multiplexed secondary never inherits # the default profile's bank/mode. Tuning knobs (timeouts, budget) stay process-global. diff --git a/tests/plugins/memory/test_hindsight_provider.py b/tests/plugins/memory/test_hindsight_provider.py index e5239866be..3bb81b4e49 100644 --- a/tests/plugins/memory/test_hindsight_provider.py +++ b/tests/plugins/memory/test_hindsight_provider.py @@ -1540,6 +1540,23 @@ def test_save_config_sets_owner_only_permissions(tmp_path): assert mode == 0o600, f"Expected 0o600 (owner-only), got {oct(mode)}" +def test_load_config_corrupt_profile_file_falls_through_to_env(tmp_path, monkeypatch): + """A corrupt $HERMES_HOME/hindsight/config.json is not the config: the loader falls through + (legacy file, then env) instead of returning an empty, silently-unconfigured mapping.""" + home = tmp_path / "home" + (home / "hindsight").mkdir(parents=True) + (home / "hindsight" / "config.json").write_text("{not json", encoding="utf-8") + monkeypatch.setenv("HERMES_HOME", str(home)) + monkeypatch.setattr(Path, "home", lambda: tmp_path / "nohome") + monkeypatch.setenv("HINDSIGHT_MODE", "local") + monkeypatch.setenv("HINDSIGHT_BANK_ID", "from-env") + + cfg = _load_config() + + assert cfg["mode"] == "local" + assert cfg["banks"]["hermes"]["bankId"] == "from-env" + + class TestLoadSimpleEnv: def test_bom_first_key_is_recognized(self, tmp_path): """A Notepad-edited .env carries a BOM; the first key must still parse