Summary
_load_skill_payload() in agent/skill_commands.py reconstructs skill_dir using SKILLS_DIR / Path(skill_path).parent, which produces a wrong path for external skills registered via skills.external_dirs. This causes the agent to be unable to locate and execute skill scripts.
Affected files / lines
agent/skill_commands.py:71-77
tools/skills_tool.py — skill_view() result dict (missing absolute skill_dir)
Root Cause
skill_view() returns path as a relative path (e.g. migrate-to-memorylake/SKILL.md). _load_skill_payload() rebuilds the absolute skill directory as:
skill_dir = SKILLS_DIR / Path(skill_path).parent
For builtin skills under ~/.hermes/skills/, this works. For external skills (e.g. a plugin registering skills from ~/.hermes/hermes-agent/plugins/memory/memorylake/skills/), this produces ~/.hermes/skills/migrate-to-memorylake/ — a directory that does not exist.
Meanwhile, skill_view() internally has the correct absolute skill_dir (skill_md.parent) but does not include it in the returned result dict.
Impact
_build_skill_message() receives a wrong skill_dir, so the fallback file discovery (lines 166-173) silently fails when linked_files is empty
- SKILL.md content referencing
{skill_dir} or SKILL_DIR for script paths cannot be resolved by the model — the model has no way to determine the absolute path of external skill scripts
- The model resorts to guessing paths (
~/.hermes/skills/..., ~/.hermes/plugins/...), running find commands, or giving up and writing its own replacement script from scratch
Reproduction
- Register an external skill directory via plugin or config:
skills:
external_dirs:
- /path/to/my-plugin/skills
- Create a skill with a script:
my-plugin/skills/my-skill/SKILL.md + scripts/run.py
- SKILL.md references
{skill_dir}/scripts/run.py or SKILL_DIR/scripts/run.py
- Invoke the skill via
/my-skill in CLI or gateway
- Observe: the model cannot locate
scripts/run.py because skill_dir in the message context points to ~/.hermes/skills/my-skill/ (does not exist)
Suggested Fix
Have skill_view() include the absolute skill_dir in its result:
result = {
...
"skill_dir": str(skill_dir) if skill_dir else None,
...
}
Then in _load_skill_payload(), prefer it over the reconstructed path:
skill_dir = None
skill_dir_str = loaded_skill.get("skill_dir")
if skill_dir_str:
skill_dir = Path(skill_dir_str)
elif skill_path:
skill_dir = SKILLS_DIR / Path(skill_path).parent
This also benefits builtin skills — the path is authoritative rather than reconstructed.
Related Issues
Summary
_load_skill_payload()inagent/skill_commands.pyreconstructsskill_dirusingSKILLS_DIR / Path(skill_path).parent, which produces a wrong path for external skills registered viaskills.external_dirs. This causes the agent to be unable to locate and execute skill scripts.Affected files / lines
agent/skill_commands.py:71-77tools/skills_tool.py—skill_view()result dict (missing absoluteskill_dir)Root Cause
skill_view()returnspathas a relative path (e.g.migrate-to-memorylake/SKILL.md)._load_skill_payload()rebuilds the absolute skill directory as:For builtin skills under
~/.hermes/skills/, this works. For external skills (e.g. a plugin registering skills from~/.hermes/hermes-agent/plugins/memory/memorylake/skills/), this produces~/.hermes/skills/migrate-to-memorylake/— a directory that does not exist.Meanwhile,
skill_view()internally has the correct absoluteskill_dir(skill_md.parent) but does not include it in the returned result dict.Impact
_build_skill_message()receives a wrongskill_dir, so the fallback file discovery (lines 166-173) silently fails whenlinked_filesis empty{skill_dir}orSKILL_DIRfor script paths cannot be resolved by the model — the model has no way to determine the absolute path of external skill scripts~/.hermes/skills/...,~/.hermes/plugins/...), runningfindcommands, or giving up and writing its own replacement script from scratchReproduction
my-plugin/skills/my-skill/SKILL.md+scripts/run.py{skill_dir}/scripts/run.pyorSKILL_DIR/scripts/run.py/my-skillin CLI or gatewayscripts/run.pybecauseskill_dirin the message context points to~/.hermes/skills/my-skill/(does not exist)Suggested Fix
Have
skill_view()include the absoluteskill_dirin its result:Then in
_load_skill_payload(), prefer it over the reconstructed path:This also benefits builtin skills — the path is authoritative rather than reconstructed.
Related Issues
SKILLS_DIR-only assumptions)external_dirsrelative path resolution depends on cwd