NousResearch/hermes-agent

External skills missing from Telegram/Discord slash commands

Cluster 8110. Data as of

Nothing open on GitHub. Fix merged in #18741 on 2 May 2026. Check it covers your case.

The short answer

0open issues
0open PRs
3items grouped
Known?
Yes. 3 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #18741. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #18741. That is separate from the fix event.
Still open
Nothing open on GitHub.

A bug where skills loaded from external directories are excluded from slash command registration in Telegram and Discord gateways. The members include a bug report and two fix attempts addressing this registration gap.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #18741 fix(gateway): include external_dirs skills in Telegram/Discord slash commands (salvage #8790)

    merged to main

    files: hermes_cli/commands.py, tests/hermes_cli/test_commands.py

    Show the diff
    • hermes_cli/commands.py
      @@ -611,13 +611,26 @@ def _collect_gateway_skill_entries(
           try:
               from agent.skill_commands import get_skill_commands
               from tools.skills_tool import SKILLS_DIR
      +        from agent.skill_utils import get_external_skills_dirs
               _skills_dir = str(SKILLS_DIR.resolve())
      -        _hub_dir = str((SKILLS_DIR / ".hub").resolve())
      +        _hub_dir = str((SKILLS_DIR / ".hub").resolve()).rstrip("/") + "/"
      +        # Build set of allowed directory prefixes: local skills dir + any
      +        # user-configured ``skills.external_dirs``. Ensure each prefix ends
      +        # with ``/`` so ``/my-skills`` does not also match ``/my-skills-extra``.
      +        # Without this widening, external skills are visible in
      +        # ``hermes skills list`` and the agent's ``/skill-name`` dispatch but
      +        # silently excluded from gateway slash menus (#8110).
      +        _allowed_prefixes = [_skills_dir.rstrip("/") + "/"]
      +        _allowed_prefixes.extend(
      +            str(d).rstrip("/") + "/" for d in get_external_skills_dirs()
      +        )
               skill_cmds = get_skill_commands()
               for cmd_key in sorted(skill_cmds):
                   info = skill_cmds[cmd_key]
                   skill_path = info.get("skill_md_path", "")
      -            if not skill_path.startswith(_skills_dir):
      +            if not skill_path:
      +                continue
      +            if not any(skill_path.startswith(prefix) for prefix in _allowed_prefixes):
                       continue
                   if skill_path.startswith(_hub_dir):
                       continue
    • tests/hermes_cli/test_commands.py
      @@ -899,6 +899,73 @@ def test_excludes_telegram_disabled_skills(self, tmp_path, monkeypatch):
               assert "my_enabled_skill" in menu_names
               assert "my_disabled_skill" not in menu_names
       
      +    def test_external_dir_skills_included_in_telegram_menu(self, tmp_path, monkeypatch):
      +        """External skills (``skills.external_dirs``) must appear in the Telegram menu.
      +
      +        Regression test for #8110 — external skills were visible to the
      +        agent and CLI but silently excluded from gateway slash menus
      +        because ``_collect_gateway_skill_entries`` only accepted skills
      +        whose path started with ``SKILLS_DIR``.
      +
      +        Also verifies the trailing-slash boundary: a directory that
      +        simply shares a prefix with a configured ``external_dirs`` entry
      +        (``/tmp/my-skills-extra`` vs ``/tmp/my-skills``) must NOT be
      +        admitted.
      +        """
      +        from unittest.mock import patch
      +
      +        local_dir = tmp_path / "skills"
      +        local_dir.mkdir()
      +        external_dir = tmp_path / "my-skills"
      +        external_dir.mkdir()
      +        lookalike_dir = tmp_path / "my-skills-extra"
      +        lookalike_dir.mkdir()
      +
      +        monkeypatch.setenv("HERMES_HOME", str(tmp_path))
      +        (tmp_path / "config.yaml").write_text(
      +            f"skills:\n  external_dirs:\n    - {external_dir}\n"
      +        )
      +
      +        fake_cmds = {
      +            "/local-one": {
      +                "name": "local-one",
      +                "description": "Local",
      +                "skill_md_path": f"{local_dir}/local-one/SKILL.md",
      +                "skill_dir": f"{local_dir}/local-one",
      +            },
      +            "/morning-briefing": {
      +                "name": "morning-briefing",
      +                "description": "External skill",
      +                "skill_md_path": f"{external_dir}/morning-briefing/SKILL.md",
      +                "skill_dir": f"{external_dir}/morning-briefing",
      +            },
      +            "/lookalike-skill": {
      +                "name": "lookalike-skill",
      +                "description": "Lives in a sibling dir that shares a prefix",
      +                "skill_md_path": f"{lookalike_dir}/lookalike-skill/SKILL.md",
      +                "skill_dir": f"{lookalike_dir}/lookalike-skill",
      +            },
      +        }
      +
      +        with (
      +            patch("agent.skill_commands.get_skill_commands", return_value=fake_cmds),
      +            patch("tools.skills_tool.SKILLS_DIR", local_dir),
      +            patch(
      +                "agent.skill_utils.get_external_skills_dirs",
      +                return_value=[external_dir],
      +            ),
      +        ):
      +            menu, _ = telegram_menu_commands(max_commands=100)
      +
      +        menu_names = {n for n, _ in menu}
      +        assert "local_one" in menu_names, "local skill must appear"
      +        assert "morning_briefing" in menu_names, (
      +            "external skill from skills.external_dirs must appear (fixes #8110)"
      +        )
      +        assert "lookalike_skill" not in menu_names, (
      +            "prefix-match sibling directories must not be admitted"
      +        )
      +
           def test_special_chars_in_skill_names_sanitized(self, tmp_path, monkeypatch):
               """Skills with +, /, or other special chars produce valid Telegram names."""
               from unittest.mock import patch

1 PR closed without merging

From the close comments: other 1

  • #8790 fix(gateway): include external_dirs skills in Telegram/Discord slash command registration · other → #18741
Closed issues · 1
  • Closed#8110Bug: Telegram slash command registration excludes external skills from skills.external_dirs
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.67: the share of item 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.

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 Lab

At a glance

Merged PRs
#18741
Closed issues
1
Attempts
1 PR closed without merging
Fix link checked
2 May 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.

For your agent

The same facts, typed.