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
Open PRs0
Fixes on record
-
#18741 fix(gateway): include external_dirs skills in Telegram/Discord slash commands (salvage #8790)
files:
hermes_cli/commands.py,tests/hermes_cli/test_commands.pyShow 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.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
- 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.