# NousResearch/hermes-agent · External skills missing from Telegram/Discord slash commands

## Open now

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

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #18741. Verify it addresses your case.
- **Recommended:** read PR #18741 (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: `resolved` · deciding PR(s): `pr-18741`. 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.67`: 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

- #8110: Bug: Telegram slash command registration excludes external skills from skills.external_dirs

## Prior & competing PRs

- **#8790: fix(gateway): include external_dirs skills in Telegram/Discord slash command registration**. closed without merge · fix tier `suggested`
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#18741`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#18741: fix(gateway): include external_dirs skills in Telegram/Discord slash commands (salvage #8790)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `hermes_cli/commands.py`, `tests/hermes_cli/test_commands.py`
  - fix hunks:
  - `hermes_cli/commands.py`

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

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

## Fix status

- Merged to main in PR #18741 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-02`. This confirmation is old and may be out of date (a revert/reopen would not have invalidated it here); re-verify.
