Skip to content

fix(skill_manager): follow symlinks when discovering skills (#35184) - #35244

Open
Linux2010 wants to merge 3 commits into
NousResearch:mainfrom
Linux2010:fix/skill-rglob-symlink-followlinks-35184
Open

fix(skill_manager): follow symlinks when discovering skills (#35184)#35244
Linux2010 wants to merge 3 commits into
NousResearch:mainfrom
Linux2010:fix/skill-rglob-symlink-followlinks-35184

Conversation

@Linux2010

@Linux2010 Linux2010 commented May 30, 2026

Copy link
Copy Markdown
Contributor

What broke

_find_skill() and _find_skill_in_other_profiles() in tools/skill_manager_tool.py used Path.rglob("SKILL.md") to discover skills. Python's pathlib.rglob() does NOT follow symbolic links into directories. This caused skill_manage to fail with "Skill not found in active profile" for any skill located under a symlinked category directory.

Meanwhile, prompt_builder.py uses iter_skill_index_files()os.walk(followlinks=True), which DOES follow symlinks. This inconsistency meant skills appeared in the system prompt index but could not be managed via skill_manage.

Root cause

rglob() does not follow symlinks by design. The two discovery paths (skill_manager_tool.py vs prompt_builder.py) used different mechanisms, and the rglob path missed symlinked skills.

Why this fix is minimal

Replaces rglob("SKILL.md") with iter_skill_index_files(skills_dir, "SKILL.md") from agent.skill_utils, which already uses os.walk(followlinks=True) and handles EXCLUDED_SKILL_DIRS filtering. Only two call sites changed; no new dependencies or behavioral changes.

What I tested

Added TestFindSkillSymlinks class with two tests:

  • test_symlinked_skill_dir_is_found — verifies a skill under a symlinked category directory is discovered
  • test_nested_symlinked_skill_is_found — verifies deeply nested skills under symlinked dirs are discovered

What I intentionally did not change

  • The security symlink-escape guards (write/remove still blocked)
  • Any other skill discovery code paths
  • External skill dirs behavior

@alt-glitch alt-glitch added type/bug Something isn't working tool/skills Skills system (list, view, manage) P2 Medium — degraded but workaround exists labels May 30, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Competes with #35213 (same fix for #35184 — both replace rglob with iter_skill_index_files). Related to long-running #12624 and #7634.

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approve

👍 Looks Good

  • Correctly replaces rglob("SKILL.md") with iter_skill_index_files(skills_dir, "SKILL.md") which uses os.walk(followlinks=True) — fixes the inconsistent behavior where skills appeared in the system prompt index but were invisible to skill_manage
  • Minimal change: only 2 call sites modified in _find_skill() and _find_skill_in_other_profiles(), 4 lines each (+1 import, -1 rglob, +1 iter_skill_index_files)
  • Reuses existing iter_skill_index_files() which already handles EXCLUDED_SKILL_DIRS filtering — no new exclusion logic needed
  • Tests properly mock the skills directory and verify symlinked (including nested) skills are discovered
  • Uses tmp_path with symlink_to for clean, isolated test setup

✅ Verified

  • 45 lines of tests covering both flat and nested symlink scenarios
  • No behavioral change for non-symlinked skills
  • Consistent with iter_skill_index_files() already used in prompt_builder.py

Reviewed by Hermes Agent

@alpindiay

Copy link
Copy Markdown

Automated Triage: Competing PRs Detected

Three PRs are independently fixing the same issue (symlink-discovery in skills, #35184):

They all modify tools/skill_management.py — only one can merge cleanly. Please coordinate and consolidate into a single PR.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for aligning skill_manage discovery with the shared skill indexer. The current-main premise is valid: tools/skill_manager_tool.py:597 and :661 still use rglob("SKILL.md"), while agent/skill_utils.py:796 uses os.walk(..., followlinks=True).

Problems

  • The new tests create symlinks without a platform-availability guard. Existing tests use pytest.skip after OSError/NotImplementedError (tests/agent/test_skill_utils.py:376-378); use the same pattern so restricted Windows environments do not fail the suite.
  • Both discovery functions are changed, but the added tests only call _find_skill. Please cover _find_skill_in_other_profiles() as well; its current-main rglob site is tools/skill_manager_tool.py:661.

Suggested changes

  • Add a cross-profile symlink regression test and platform-safe symlink setup.

Automated hermes-sweeper review.

real_category = tmp_path / "real_category" / "my-skill"
real_category.mkdir(parents=True)
(real_category / "SKILL.md").write_text(VALID_SKILL_CONTENT)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please catch OSError and NotImplementedError here and pytest.skip when symlinks cannot be created. Existing symlink coverage uses that guard in tests/agent/test_skill_utils.py:376-378, which keeps restricted Windows environments from failing the suite.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026
Linux2010 and others added 3 commits July 26, 2026 19:11
…arch#35184)

_find_skill() and _find_skill_in_other_profiles() used Path.rglob()
which does NOT follow symbolic links. This caused skills under symlinked
category directories to be invisible to skill_manage, even though they
appeared in the system prompt index (prompt_builder.py uses
os.walk(followlinks=True) via iter_skill_index_files).

Fix: replace rglob("SKILL.md") with iter_skill_index_files(skills_dir,
"SKILL.md") from agent.skill_utils, which already uses
os.walk(followlinks=True) and handles EXCLUDED_SKILL_DIRS filtering.

This aligns skill discovery in skill_manager_tool.py with how
prompt_builder.py already discovers skills for the system prompt.

Test: add TestFindSkillSymlinks class with two tests:
- test_symlinked_skill_dir_is_found — verifies a skill under a symlinked
  category directory is discovered
- test_nested_symlinked_skill_is_found — verifies deeply nested skills
  under symlinked dirs are discovered
- Add linux2010@github.com to AUTHOR_MAP for check-attribution
- Fix test_symlinked_skill_dir_is_found: iter_skill_index_files
  returns path through symlink, not the target path

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e test

Address sweeper review feedback on NousResearch#35244:
- Wrap symlink_to() calls in try/except OSError → pytest.skip for
  restricted Windows environments (matching pattern in test_skill_utils.py)
- Add test_symlinked_skill_found_via_other_profile to cover
  _find_skill_in_other_profiles() symlink path

Refs NousResearch#35244
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data tool/skills Skills system (list, view, manage) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants