# NousResearch/hermes-agent · Fixes for null/scalar skills config parsing bugs

## Open now

- 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.

## TL;DR

- 8 member(s): 0 open issue(s), 2 closed issue(s), 0 open PR(s), 1 merged PR(s), 5 PR(s) closed without merging.
- A fix was merged to main in PR #61797. Verify it addresses your case.
- **Recommended:** read PR #61797 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-61797`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.26`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #13026: \[Bug\]: get_disabled_skills crashes on \`skills: null\` and splits scalar names into characters
- #15486: Bug: Web server returns 500 error when skills configuration returns None

## Prior & competing PRs

- **#61797: fix(cli): get_disabled_skills handles null and scalar skills config (#13026)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `hermes_cli/skills_config.py`, `tests/hermes_cli/test_skills_config.py`
  - fix hunks:
  - `hermes_cli/skills_config.py`

    ```diff
    @@ -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.
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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()
    ```
- **#13047: fix(cli): normalize disabled skills config parsing**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `4c03032a240a297ce6603bf352cf76d88f59d75f`, `#61797`, `#61797`, `#13026`, `#13078`, `#50648`, `#61797`
- **#13078: fix(skills_config): handle null/scalar values in get_disabled_skills**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#61797`, `#13026`
- **#14094: fix(skills-config): normalize disabled skill entries**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#61797`, `#13078`, `#14094`
- **#15501: fix: handle None skills config in skills_config helpers**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `4c03032a240a297ce6603bf352cf76d88f59d75f`, `#61797`, `#13078`, `#13078`, `#61797`
- **#50648: fix(cli): tolerate null/scalar values in get_disabled_skills**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`

## Fix status

- Merged to main in PR #61797 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-07-10`.
- Subsystem: `hermes_cli`
