Skip to content

Bug: _load_skill_payload rebuilds wrong skill_dir for external skills (from skills.external_dirs) #10313

@HQidea

Description

@HQidea

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

  1. _build_skill_message() receives a wrong skill_dir, so the fallback file discovery (lines 166-173) silently fails when linked_files is empty
  2. 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
  3. 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

  1. Register an external skill directory via plugin or config:
    skills:
      external_dirs:
        - /path/to/my-plugin/skills
  2. Create a skill with a script: my-plugin/skills/my-skill/SKILL.md + scripts/run.py
  3. SKILL.md references {skill_dir}/scripts/run.py or SKILL_DIR/scripts/run.py
  4. Invoke the skill via /my-skill in CLI or gateway
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions