Repository navigation
Description
Bug Description
With skills.create_dir configured (added in #100377), skills in that directory are advertised in the system-prompt skill index but cannot be loaded or listed:
skill_view(name)returnsSkill '<name>' not found.skills_listomits them- no
/<skill-name>slash command is registered, and they are missing from the Telegram / Discord skill menus
skill_manage can create and patch them fine. So the agent creates a skill with skill_manage, sees it offered in its prompt on the next turn, then fails to load it.
Steps to Reproduce
This runs against a fresh synthetic HERMES_HOME. No gateway or provider is needed.
# repro_create_dir.py — usage: python repro_create_dir.py <hermes-agent checkout>
import json, os, sys, tempfile
from pathlib import Path
repo = Path(sys.argv[1]).resolve()
sys.path.insert(0, str(repo))
root = Path(tempfile.mkdtemp())
home = root / ".hermes"
(home / "skills").mkdir(parents=True)
brain = root / "brain-skills"
(home / "config.yaml").write_text(f"skills:\n create_dir: {brain}\n")
os.environ["HERMES_HOME"] = str(home)
import tools.skills_tool as st
from tools.skill_manager_tool import skill_manage
from agent.prompt_builder import build_skills_system_prompt
created = json.loads(skill_manage("create", "demo-skill", content=(
"---\nname: demo-skill\n"
"description: Use when demonstrating the create_dir lookup gap.\n"
"---\n\n# demo-skill\n\nBody.\n")))
print("skill_manage create :", created.get("success"), created.get("path"))
print("in prompt index :", "demo-skill" in build_skills_system_prompt())
print("skills_list names :", [s["name"] for s in json.loads(st.skills_list())["skills"]])
view = json.loads(st.skill_view("demo-skill"))
print("skill_view :", "ok" if view.get("success") else view.get("error"))Expected Behavior
Any skill the prompt index advertises can be loaded with skill_view, shows up in skills_list, and gets a slash command. In other words, every discovery surface scans the same directories.
Actual Behavior
On main @ 8c74118c4a:
skill_manage create : True /tmp/tmp2g9yr61c/brain-skills/demo-skill
in prompt index : True
skills_list names : []
skill_view : Skill 'demo-skill' not found.
Affected Component
Tools (skill_view, skills_list), slash-command registration, gateway skill menus (Telegram / Discord).
Debug Report
N/A. Reproduced with the standalone script above on a fresh synthetic HERMES_HOME, with no gateway, provider or session involved.
Operating System
Linux (Ubuntu, kernel 6.8)
Python Version
3.12.3
Hermes Version
main @ 8c74118c4a. Also reproduced on the v0.21.1 docker image.
Root Cause Analysis
agent/skill_utils.py::get_all_skills_dirs() returns local → create_dir → external_dirs. Some callers use it; others build the list by hand as local + get_external_skills_dirs(), which leaves create_dir out:
| Call site | Directory list | Sees create_dir? |
|---|---|---|
agent/prompt_builder.py::build_skills_system_prompt |
get_all_skills_dirs()[1:] |
✅ |
tools/skill_manager_tool.py::_find_skill (patch/edit) |
get_all_skills_dirs() |
✅ |
tools/skill_manager_tool.py::_resolve_skill_dir (create) |
get_skill_create_dir() |
✅ |
tools/skills_tool.py::_skill_search_dirs (skill_view, skills_list) |
local + get_external_skills_dirs() |
❌ |
tools/skills_tool.py::_get_category_from_path |
local + get_external_skills_dirs() |
❌ |
agent/skill_commands.py::scan_skill_commands |
local + get_external_skills_dirs() |
❌ |
hermes_cli/commands_platforms.py::_iter_gateway_skills |
local + get_external_skills_dirs() |
❌ |
The tests added in #100377 check get_all_skills_dirs() and _find_skill but no read path, which is how the gap got past review.
The obvious workaround has a trap. Adding the create_dir path to skills.external_dirs as well makes skill_view find the skills, but is_external_skill_path() then treats that directory as externally owned:
_background_review_write_guardrefuses every background-review / curator write to those skills, so the self-improvement loop can no longer patch the skills it created.- The curator passes in
tools/skill_usage.pyskip them. - With the open fix(skills): classify external_dirs as external and default-deny in-place writes #108040 (default-deny foreground writes under
external_dirs), foregroundskill_managepatch/edit would be refused too.
So the fix belongs in the read paths, not in config.
Proposed Fix
Switch the four ❌ sites to get_all_skills_dirs()[1:], the same form prompt_builder and skill_usage already use. get_all_skills_dirs() already removes duplicates between create_dir and external_dirs, and _collect_skill_candidates removes duplicates by resolved path, so a directory listed under both keys stays unambiguous. A PR with regression tests follows.
Out of scope, noted for follow-up:
tools/credential_files.py::_skill_dir_roots(the remote-sandbox skill sync) also enumerates local + external + project dirs withoutcreate_dir. So acreate_dirskill's bundled files look like they are not synced to remote terminal backends. It uses a different mount layout, so I left it out; not verified end to end.hermes_cli/commands.pystill holds legacy copies of_collect_gateway_skill_entries/discord_skill_commandswith the same pattern. No live caller uses them (the compat map points atcommands_platforms), and they are scheduled for removal on 2026-09-14.
Are you willing to submit a PR for this?
- I'd like to fix this myself and submit a PR
Activity
Enough1122 commentedon Sep 27, 2026
Ran your script on main (b4410b4) verbatim; it reproduces unchanged - create reports True, the
prompt index contains the skill,
skills_listreturns[],skill_viewreturns "not found". Thecause is a one-line divergence between two dir resolvers: the prompt builder goes through
agent/skill_utils.py:418-426 (
get_all_skills_dirs), which appendscreate_dir, while the toollayer does not - tools/skills_tool.py:173-181 (
_skill_search_dirs) composesproject_dirs+[active_skills_dir]+get_external_skills_dirs, and agent/skill_utils.py:357-382(
get_external_skills_dirs) reads onlyskills.external_dirs, never callingget_skill_create_dir. Withcreate_dirset the two disagree - the first returns both dirs, thesecond
[]- and sinceskills_list(:234-238) andskill_view(:593-596) both route through_skill_search_dirs, the slash commands and gateway skill menus inherit the gap. The fix wantsget_external_skills_dirsto includeget_skill_create_dir()on the read side, so every discoverysurface scans what the prompt index already advertises.
Or-well commentedon Oct 1, 2026
Independent reproduction on a live instance, plus a workaround that restores every read surface
Environment: release
v0.21.5+5014.gaf92ea8(installaf92ea8e5b). Re-checked onmain(e8c97320ac, 2026-10-01):tools/skills_tool.py::_skill_search_dirsstill composesproject + local + get_external_skills_dirs(), andagent/skill_utils.py::get_external_skills_dirsstill reads onlyskills.external_dirs(noget_skill_create_dir) — so the divergence is unchanged on main, while the docs still promise the integrated behaviour (website/docs/user-guide/features/skills.mdL474: "The directory is fully integrated. Skills undercreate_dir… appear in the skill index,skills_list,skill_view, slash commands").Reproduction on a real profile, not a synthetic
HERMES_HOME: 90 user-authored skills live underskills.create_dir: ~/.hermes/skills-local, andskills.external_dirsdid not list that directory.create_dironlyagent/prompt_builder.py)skills_list(category="autonomous-ai-agents")(tools/skills_tool.py:234)skill_view("<moved-skill>")(tools/skills_tool.py:593)Skill '<name>' not found.hermes skills listCLI (hermes_cli/skills_hub.py::do_list)skill_managecreate / patch / delete (tools/skill_manager_tool.py)create_dir(verified with a throwaway skill)That is exactly the "created → advertised → unloadable" state reported here: the agent sees the skill in its prompt index and then cannot load it.
Workaround until the fix lands — keep
create_dirfor writes and additionally list the same path inskills.external_dirs:With both keys pointing at the same directory every read surface recovers:
hermes skills listbecomes byte-identical to the pre-move state (157 = 30 builtin + 24 official + 90 user-authored + 13 shared),skills_listandskill_viewresolve, andskill_managestill creates intocreate_dir. The duplicated entry is harmless —get_all_skills_dirs()already dedupes, and #108160 keeps that dedupe covered by a regression test.Re #108160: that PR addresses precisely this divergence (moving the read call sites to
get_all_skills_dirs()[1:]). It has been open since 2026-09-11 with no review or checks; I'm happy to verify a rebase against currentmainon a live instance (90 skills undercreate_dir, all surfaces re-checked) if that helps move it forward.neo-wanderer commentedon Oct 4, 2026
Fixed on
mainby #132464 — f525ecd (skill_view/skills_list) and 340be37 (slash commands). The repro above passes from 340be37 onward. Closing; #108160 is closed as superseded.