From c1115a7166d12385c0e682861ffdcbbdd9f93f61 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:01:34 -0700 Subject: [PATCH] fix(config): treat empty-dict DEFAULT_CONFIG sections as open containers in the typo gate compression.model_thresholds., terminal.docker_env., lsp.servers..*, auxiliary..extra_body. and similar free-form mappings are declared as {} in DEFAULT_CONFIG; the fail-closed gate walked into the empty dict, found the user-chosen key missing and refused the write. An empty dict now accepts the rest of the path, like a scalar leaf or a platforms container does. Populated sections keep the did-you-mean refusal. --- hermes_cli/config.py | 10 ++++++---- tests/hermes_cli/test_config.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 9d8849695d..c04c65313f 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -3298,13 +3298,15 @@ def _validate_config_key(key: str) -> tuple[bool, Optional[str]]: if top in _OPEN_SUBKEY_TOP_LEVEL_KEYS: return True, None - # Walk DEFAULT_CONFIG: a nested ``platforms`` container or a scalar leaf hit before the path is - # consumed both accept (the latter matches set_config_value's leaf->dict replacement); an - # unknown sub-key fails with a same-level "did you mean" suggestion. + # Walk DEFAULT_CONFIG: a nested ``platforms`` container, a scalar leaf, or an EMPTY dict hit + # before the path is consumed all accept. An empty dict is a free-form mapping section + # (``compression.model_thresholds.``, ``terminal.docker_env.``, + # ``lsp.servers.``): its keys are user-chosen, so nothing under it can be a typo. An + # unknown sub-key of a populated section fails with a same-level "did you mean" suggestion. node: Any = DEFAULT_CONFIG.get(top) consumed = [top] for seg in segments[1:]: - if seg in _PLATFORM_CONTAINER_KEYS or not isinstance(node, dict): + if seg in _PLATFORM_CONTAINER_KEYS or not isinstance(node, dict) or not node: return True, None if seg not in node: sibling = _suggest_closest_key(seg, set(node.keys())) diff --git a/tests/hermes_cli/test_config.py b/tests/hermes_cli/test_config.py index ff1a137163..96bc612e8b 100644 --- a/tests/hermes_cli/test_config.py +++ b/tests/hermes_cli/test_config.py @@ -1941,3 +1941,20 @@ def test_gateway_multiplex_keys_are_recognized_config_keys(): known, suggestion = _validate_config_key("gateway.auto_migrate") assert known is False assert suggestion == "gateway.auto_multiplex_migration" + + +def test_empty_dict_default_sections_are_open_containers(): + """``compression.model_thresholds.`` / ``terminal.docker_env.`` are free-form + mappings declared as ``{}`` in DEFAULT_CONFIG: their user-chosen keys must not be refused as + typos, while a real typo under a populated sibling section still gets a suggestion.""" + from hermes_cli.config import _validate_config_key + from hermes_cli.config_defaults import DEFAULT_CONFIG + assert DEFAULT_CONFIG["compression"]["model_thresholds"] == {} + assert DEFAULT_CONFIG["terminal"]["docker_env"] == {} + assert _validate_config_key("compression.model_thresholds.gpt-5") == (True, None) + assert _validate_config_key("terminal.docker_env.FOO") == (True, None) + assert _validate_config_key("lsp.servers.python.command") == (True, None) + assert _validate_config_key("auxiliary.vision.extra_body.reasoning") == (True, None) + known, suggestion = _validate_config_key("compression.model_threshold.gpt-5") + assert known is False + assert suggestion == "compression.model_thresholds"