Fixes for null/scalar skills config parsing bugs
NousResearch/hermes-agent · cluster 13026 · hermes cli
Data as of
Nothing open on GitHub. Fix merged in #61797. Check it covers your case.
The short answer
- Known?
- Yes. 8 related items grouped here, 2 issues already closed.
- Fix event
- A fix was merged to main in PR #61797. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #61797. That is separate from the fix event. - Still open
- Nothing open on GitHub.
Multiple fixes addressing crashes and incorrect behavior when the skills configuration is null, None, or a scalar value instead of the expected list or dict. Affects both CLI parsing and web server handling of disabled skills.
Open issues0
Open PRs0
Fixes on record
-
#61797 fix(cli): get_disabled_skills handles null and scalar skills config (#13026)
files:
hermes_cli/skills_config.py,tests/hermes_cli/test_skills_config.pyShow the diff
-
hermes_cli/skills_config.py@@ -24,6 +24,23 @@ # ─── Config Helpers ─────────────────────────────────────────────────────────── +def _normalize_skill_names(values) -> Set[str]: + """Normalize a config value into a set of skill names. + + Mirrors ``agent.skill_utils._normalize_string_set``: ``None`` (YAML null) + means empty, a bare scalar (``disabled: my-skill``) means a single-item + list — NOT a set of its characters (#13026). + """ + if values is None: + return set() + if isinstance(values, str): + values = [values] + try: + return {str(v).strip() for v in values if str(v).strip()} + except TypeError: + return set() + + def get_disabled_skills(config: dict, platform: Optional[str] = None) -> Set[str]: """Return disabled skill names: the global list unioned with the platform-specific list when a platform is given. @@ -32,14 +49,16 @@ def get_disabled_skills(config: dict, platform: Optional[str] = None) -> Set[str platform list adds to the global list rather than replacing it. This mirrors ``agent.skill_utils.get_disabled_skill_names``. """ - skills_cfg = config.get("skills", {}) - global_disabled = set(skills_cfg.get("disabled", [])) + skills_cfg = config.get("skills") or {} + if not isinstance(skills_cfg, dict): + return set() + global_disabled = _normalize_skill_names(skills_cfg.get("disabled")) if platform is None: return global_disabled platform_disabled = cfg_get(skills_cfg, "platform_disabled", platform) if platform_disabled is None: return global_disabled - return global_disabled | set(platform_disabled) + return global_disabled | _normalize_skill_names(platform_disabled) def save_disabled_skills(config: dict, disabled: Set[str], platform: Optional[str] = None): -
tests/hermes_cli/test_skills_config.py@@ -46,6 +46,34 @@ def test_missing_skills_key(self): from hermes_cli.skills_config import get_disabled_skills assert get_disabled_skills({"other": "value"}) == set() + def test_null_skills_section(self): + """``skills:`` with no value (YAML null) must not crash (#13026).""" + from hermes_cli.skills_config import get_disabled_skills + assert get_disabled_skills({"skills": None}) == set() + assert get_disabled_skills({"skills": None}, platform="telegram") == set() + + def test_null_disabled_key(self): + from hermes_cli.skills_config import get_disabled_skills + assert get_disabled_skills({"skills": {"disabled": None}}) == set() + + def test_scalar_disabled_is_single_skill_not_characters(self): + """``disabled: my-skill`` (bare scalar) is one skill name, not a + set of its characters (#13026).""" + from hermes_cli.skills_config import get_disabled_skills + assert get_disabled_skills({"skills": {"disabled": "my-skill"}}) == {"my-skill"} + + def test_scalar_platform_disabled(self): + from hermes_cli.skills_config import get_disabled_skills + config = {"skills": { + "disabled": ["global-skill"], + "platform_disabled": {"telegram": "tg-skill"}, + }} + assert get_disabled_skills(config, platform="telegram") == {"global-skill", "tg-skill"} + + def test_non_dict_skills_section(self): + from hermes_cli.skills_config import get_disabled_skills + assert get_disabled_skills({"skills": "oops"}) == set() + def test_empty_disabled_list(self): from hermes_cli.skills_config import get_disabled_skills assert get_disabled_skills({"skills": {"disabled": []}}) == set()
-
5 PRs closed without merging
From the close comments: done elsewhere 4 · superseded by another PR 1
-
#13047 fix(cli): normalize disabled skills config parsing · done elsewhere →
4c03032,#61797,#13026,#13078,#50648 -
#13078 fix(skills_config): handle null/scalar values in get_disabled_skills · done elsewhere →
#61797,#13026 -
#14094 fix(skills-config): normalize disabled skill entries · done elsewhere →
#61797,#13078,#14094 -
#15501 fix: handle None skills config in skills_config helpers · done elsewhere →
4c03032,#61797,#13078 - #50648 fix(cli): tolerate null/scalar values in get_disabled_skills · superseded by another PR
Closed issues · 2
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.26: 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
- #61797
- Closed issues
- 2
- Attempts
- 5 PRs closed without merging
- Fix link checked
- 10 Jul 2026