Empty YAML section keys in CLI config handling
Cluster 58277 · hermes cli. Data as of
Nothing open on GitHub. Fix merged in #61733 on 10 Jul 2026. Check it covers your case.
The short answer
- Known?
- Yes. 4 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #61733. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #61733. That is separate from the fix event. - Still open
- Nothing open on GitHub.
One bug report describes a TypeError in load_cli_config when a profile config.yaml has an empty terminal: key. The three related pull requests each propose handling empty or null config sections, such as keeping default dicts instead of replacing them. This summary does not state whether any change was merged or fixes the problem.
Open issues0
Open PRs0
Fixes on record
-
#61733 fix(config): empty YAML section keys no longer replace default dicts (#58277)
files:
cli.py,hermes_cli/config.py,tests/hermes_cli/test_config.py,tests/hermes_cli/test_config_env_expansion.pyShow the diff
-
cli.py@@ -543,6 +543,8 @@ def load_cli_config() -> Dict[str, Any]: if key == "model": continue # Already handled above if key in file_config: + if isinstance(defaults[key], dict) and file_config[key] is None: + continue if isinstance(defaults[key], dict) and isinstance(file_config[key], dict): defaults[key].update(file_config[key]) else: -
hermes_cli/config.py@@ -6243,6 +6243,12 @@ def _deep_merge(base: dict, override: dict) -> dict: Keys in *override* take precedence. If both values are dicts the merge recurses, so a user who overrides only ``tts.elevenlabs.voice_id`` will keep the default ``tts.elevenlabs.model_id`` intact. + + An empty section key in config.yaml (``terminal:`` with no value) parses + as YAML ``None``; treating that as an override would replace the entire + default dict with ``None`` and crash every downstream consumer that + expects a mapping (#58277). A ``None`` override of a dict default is + ignored — same as the key being absent. """ result = base.copy() for key, value in override.items(): @@ -6252,6 +6258,8 @@ def _deep_merge(base: dict, override: dict) -> dict: and isinstance(value, dict) ): result[key] = _deep_merge(result[key], value) + elif key in result and isinstance(result[key], dict) and value is None: + continue else: result[key] = value return result -
tests/hermes_cli/test_config.py@@ -360,6 +360,34 @@ def test_last_known_good_cached_no_rewarn_spam(self, tmp_path, capsys): assert capsys.readouterr().err == "" +class TestEmptyConfigSections: + """Empty section keys (``terminal:`` with no value) parse as YAML None + and must not replace the default dict for that section (#58277).""" + + def test_null_section_keeps_defaults_in_load_config(self, tmp_path): + with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path)}): + (tmp_path / "config.yaml").write_text( + "model:\n default: test/custom\n" + "terminal:\n" + "display:\n" + ) + config = load_config() + assert config["model"]["default"] == "test/custom" + assert isinstance(config["terminal"], dict) + assert config["terminal"] == DEFAULT_CONFIG["terminal"] + assert isinstance(config["display"], dict) + + def test_null_override_of_non_dict_default_still_applies(self, tmp_path): + """None only shields dict defaults — explicit null for a scalar + key remains an override (unchanged behavior).""" + from hermes_cli.config import _deep_merge + + merged = _deep_merge({"scalar": 5, "section": {"a": 1}}, + {"scalar": None, "section": None}) + assert merged["scalar"] is None + assert merged["section"] == {"a": 1} + + class TestSaveAndLoadRoundtrip: @staticmethod def _deny_config_reads(config_path): -
tests/hermes_cli/test_config_env_expansion.py@@ -150,6 +150,18 @@ def test_unchanged_env_still_serves_cache(self, tmp_path, monkeypatch): class TestLoadCliConfigExpansion: """Verify that load_cli_config() also expands ${VAR} references.""" + def test_cli_config_ignores_empty_terminal_section(self, tmp_path, monkeypatch): + config_file = tmp_path / "config.yaml" + config_file.write_text("terminal:\n") + + monkeypatch.setattr("cli._hermes_home", tmp_path) + + from cli import load_cli_config + config = load_cli_config() + + assert isinstance(config["terminal"], dict) + assert config["terminal"]["env_type"] == "local" + def test_cli_config_expands_auxiliary_api_key(self, tmp_path, monkeypatch): config_yaml = ( "auxiliary:\n"
-
2 PRs closed without merging
From the close comments: done elsewhere 1 · merged via another change 1
Closed issues · 1
- Closed#58277[Bug]: Empty YAML key (terminal:) in profile config.yaml crashes load_cli_config with TypeError
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.50: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- hermes cli
- Merged PRs
- #61733
- Closed issues
- 1
- Attempts
- 2 PRs closed without merging
- Fix link checked
- 10 Jul 2026