Platform-disabled skills appearing in <available_skills> prompt
Cluster 46201 · skills. Data as of
Nothing open on GitHub. Fix merged in #46236 on 14 Jun 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 #46236. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #46236. That is separate from the fix event. - Still open
- Nothing open on GitHub.
All four members concern disabled skills still showing up in the <available_skills> prompt; one is a bug report that also mentions false loads, and three reference #46201 with proposed fixes. One of them also mentions unifying resolution sites. Their status is not established here.
Open issues0
Open PRs0
Fixes on record
-
#46236 fix(skills): platform-disabled skills still appear in <available_skills> + unify all resolution sites (#46201)
files:
agent/prompt_builder.py,agent/skill_utils.py,hermes_cli/skills_config.py,tests/hermes_cli/test_skills_config.py,tools/skills_tool.pyShow the diff
-
agent/prompt_builder.py@@ -1164,7 +1164,7 @@ def build_skills_system_prompt( or get_session_env("HERMES_SESSION_PLATFORM") or "" ) - disabled = get_disabled_skill_names() + disabled = get_disabled_skill_names(_platform_hint or None) cache_key = ( str(skills_dir.resolve()), tuple(str(d) for d in external_dirs), -
agent/skill_utils.py@@ -278,8 +278,10 @@ def get_disabled_skill_names(platform: str | None = None) -> Set[str]: Args: platform: Explicit platform name (e.g. ``"telegram"``). When *None*, resolves from ``HERMES_PLATFORM`` or - ``HERMES_SESSION_PLATFORM`` env vars. Falls back to the - global disabled list when no platform is determined. + ``HERMES_SESSION_PLATFORM`` env vars. Returns the global + disabled list, unioned with the platform-specific list when a + platform is resolved (a globally-disabled skill stays disabled + on every platform). Reads the config file directly (no CLI config imports) to stay lightweight. @@ -305,13 +307,14 @@ def get_disabled_skill_names(platform: str | None = None) -> Set[str]: or os.getenv("HERMES_PLATFORM") or get_session_env("HERMES_SESSION_PLATFORM") ) + global_disabled = _normalize_string_set(skills_cfg.get("disabled")) if resolved_platform: platform_disabled = (skills_cfg.get("platform_disabled") or {}).get( resolved_platform ) if platform_disabled is not None: - return _normalize_string_set(platform_disabled) - return _normalize_string_set(skills_cfg.get("disabled")) + return global_disabled | _normalize_string_set(platform_disabled) + return global_disabled def _normalize_string_set(values) -> Set[str]: -
hermes_cli/skills_config.py@@ -25,7 +25,13 @@ # ─── Config Helpers ─────────────────────────────────────────────────────────── def get_disabled_skills(config: dict, platform: Optional[str] = None) -> Set[str]: - """Return disabled skill names. Platform-specific list falls back to global.""" + """Return disabled skill names: the global list unioned with the + platform-specific list when a platform is given. + + A globally-disabled skill stays disabled on every platform, so the + 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", [])) if platform is None: @@ -33,7 +39,7 @@ def get_disabled_skills(config: dict, platform: Optional[str] = None) -> Set[str platform_disabled = cfg_get(skills_cfg, "platform_disabled", platform) if platform_disabled is None: return global_disabled - return set(platform_disabled) + return global_disabled | set(platform_disabled) def save_disabled_skills(config: dict, disabled: Set[str], platform: Optional[str] = None): -
tests/hermes_cli/test_skills_config.py@@ -22,7 +22,19 @@ def test_reads_platform_disabled(self): "disabled": ["skill-a"], "platform_disabled": {"telegram": ["skill-b"]} }} - assert get_disabled_skills(config, platform="telegram") == {"skill-b"} + # Union of global + platform: a globally-disabled skill stays disabled + # on every platform, and the platform list adds to it. + assert get_disabled_skills(config, platform="telegram") == {"skill-a", "skill-b"} + + def test_platform_list_unions_with_global(self): + from hermes_cli.skills_config import get_disabled_skills + config = {"skills": { + "disabled": ["global-skill"], + "platform_disabled": {"telegram": []} + }} + # An explicit empty platform list does NOT re-enable a globally-disabled + # skill (matches issue #46201 — global disables hold everywhere). + assert get_disabled_skills(config, platform="telegram") == {"global-skill"} def test_platform_falls_back_to_global(self): from hermes_cli.skills_config import get_disabled_skills @@ -102,14 +114,27 @@ def test_platform_disabled(self, mock_load): assert _is_skill_disabled("tg-skill", platform="telegram") is True @patch("hermes_cli.config.load_config") - def test_platform_enabled_overrides_global(self, mock_load): + def test_globally_disabled_stays_disabled_on_platform(self, mock_load): + mock_load.return_value = {"skills": { + "disabled": ["skill-a"], + "platform_disabled": {"telegram": ["tg-skill"]} + }} + from tools.skills_tool import _is_skill_disabled + # Union: a globally-disabled skill stays disabled on a platform that + # has its own platform_disabled list (matches issue #46201). + assert _is_skill_disabled("skill-a", platform="telegram") is True + assert _is_skill_disabled("tg-skill", platform="telegram") is True + + @patch("hermes_cli.config.load_config") + def test_empty_platform_list_keeps_global_disabled(self, mock_load): mock_load.return_value = {"skills": { "disabled": ["skill-a"], "platform_disabled": {"telegram": []} }} from tools.skills_tool import _is_skill_disabled - # telegram has explicit empty list -> skill-a is NOT disabled for telegram - assert _is_skill_disabled("skill-a", platform="telegram") is False + # An explicit empty platform list does NOT re-enable a globally-disabled + # skill — global disables hold on every platform. + assert _is_skill_disabled("skill-a", platform="telegram") is True @patch("hermes_cli.config.load_config") def test_platform_falls_back_to_global(self, mock_load): @@ -164,7 +189,7 @@ def test_explicit_platform_param(self, tmp_path, monkeypatch): from agent.skill_utils import get_disabled_skill_names result = get_disabled_skill_names(platform="telegram") - assert result == {"tg-only-skill"} + assert result == {"tg-only-skill", "global-skill"} def test_session_platform_env_var(self, tmp_path, monkeypatch): """HERMES_SESSION_PLATFORM should be used when HERMES_PLATFORM is unset.""" @@ -183,7 +208,7 @@ def test_session_platform_env_var(self, tmp_path, monkeypatch): from agent.skill_utils import get_disabled_skill_names result = get_disabled_skill_names() - assert result == {"discord-skill"} + assert result == {"discord-skill", "global-skill"} def test_hermes_platform_takes_precedence(self, tmp_path, monkeypatch): """HERMES_PLATFORM should win over HERMES_SESSION_PLATFORM.""" -
tools/skills_tool.py@@ -583,11 +583,15 @@ def _is_skill_disabled(name: str, platform: str = None) -> bool: config = load_config() skills_cfg = config.get("skills", {}) resolved_platform = platform or os.getenv("HERMES_PLATFORM") or _get_session_platform() + global_disabled = skills_cfg.get("disabled", []) if resolved_platform: platform_disabled = cfg_get(skills_cfg, "platform_disabled", resolved_platform) if platform_disabled is not None: - return name in platform_disabled - return name in skills_cfg.get("disabled", []) + # A globally-disabled skill stays disabled on every platform; + # the platform list adds to it rather than replacing it. Keep + # in sync with agent.skill_utils.get_disabled_skill_names. + return name in platform_disabled or name in global_disabled + return name in global_disabled except Exception: return False
-
2 PRs closed without merging
From the close comments: done elsewhere 2
Closed issues · 1
- Closed#46201[Bug]: Disabled skills still appear in <available_skills> prompt and trigger false loads
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
- skills
- Merged PRs
- #46236
- Closed issues
- 1
- Attempts
- 2 PRs closed without merging
- Fix link checked
- 14 Jun 2026