fix(skills): use os.walk(followlinks=True) in _find_skill for symlinked skill dirs - #44210
fix(skills): use os.walk(followlinks=True) in _find_skill for symlinked skill dirs#44210NorethSea wants to merge 2 commits into
Conversation
…ed skill dirs On Python 3.11 Path.rglob does not follow directory symlinks (the follow_symlinks parameter was added in 3.12). This makes skill_manage unable to find skills symlinked from shared directories like ~/.agents/skills/. Replace rglob with os.walk(followlinks=True) to match iter_skill_index_files which already handles this correctly.
|
✅ Verification — symlinked skill directory resolution looks correct Checked:
One note: |
|
This fix works for my setup. I have a skill installed via a category-level symlink ( I've run another test: using from agent.skill_utils import iter_skill_index_files
for skill_md in iter_skill_index_files(skills_dir, "SKILL.md"):
if skill_md.parent.name == name:
return {"path": skill_md.parent}This centralizes the exclusion logic ( Tested locally — works. |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for identifying a real inconsistency: current main still uses Path.rglob("SKILL.md") in tools/skill_manager_tool.py:597-601, while the shared index scanner follows symlinked directories in agent/skill_utils.py:796-807.
Problems
- The added inline walk at
tools/skill_manager_tool.py:302duplicates the shared scanner and does not prune excluded/support directories before descent asiter_skill_index_files()does atagent/skill_utils.py:797-803. _find_skill_in_other_profilesstill usesrglobattools/skill_manager_tool.py:661-666, so cross-profile not-found diagnostics remain inconsistent for symlinked skills.- Please add a
tests/tools/test_skill_manager_tool.pyregression test covering direct and category-level skill-directory symlinks.
Suggested changes
- Reuse
iter_skill_index_files(skills_dir, "SKILL.md")in both discovery paths, preserving the existing parent-name comparison.
Automated hermes-sweeper review.
| if os.path.basename(root) == name: | ||
| skill_md_path = Path(root) / "SKILL.md" | ||
| if is_excluded_skill_path(skill_md_path): | ||
| continue |
There was a problem hiding this comment.
Please reuse iter_skill_index_files(skills_dir, "SKILL.md") here. It already follows symlinks and prunes excluded/support directories before descent (agent/skill_utils.py:796-807); this inline walk duplicates that logic without the pruning behavior.
There was a problem hiding this comment.
Thanks for the review. I updated both _find_skill and _find_skill_in_other_profiles to reuse iter_skill_index_files(skills_dir, "SKILL.md"), so symlink traversal and excluded-directory pruning stay centralized. I also added regression coverage for direct and category-level symlinked skill directories, including the cross-profile lookup. tests/tools/test_skill_manager_tool.py passes all 93 tests. The changes are in commit 7504ed8469.
On Python 3.11, Path.rglob does not follow directory symlinks (the
follow_symlinks parameter was added in 3.12). This means skill_manage
cannot see skills that are symlinked into ~/.hermes/skills/ from shared
directories like ~/.agents/skills/.
iter_skill_index_files already uses os.walk(followlinks=True) and
correctly finds these skills. This change aligns _find_skill with that
existing pattern.
Problem
skill_view(), skills_list(), and the system prompt builder all see
symlinked skills, but skill_manage(action=patch) and all other
skill_manage actions return "Skill X not found in active profile".
Example: read-project-code is a symlink ~/.hermes/skills/read-project-code
→ ~/.agents/skills/read-project-code. It appears in skills_list() and
can be loaded via skill_view(), but skill_manage(action=patch, name=
read-project-code, ...) fails.
Changes
Replace skills_dir.rglob("SKILL.md") with os.walk(skills_dir,
followlinks=True) in _find_skill(), matching the approach already used
by iter_skill_index_files in agent/skill_utils.py.
How to test
pytest tests/tools/test_skill_manager_tool.py -v # 90 passedManually:
Platforms tested