Skip to content

fix(skills): follow directory symlinks in SKILL.md discovery - #44129

Open
Helmi wants to merge 3 commits into
NousResearch:mainfrom
Helmi:fix/skill-discovery-symlinks
Open

fix(skills): follow directory symlinks in SKILL.md discovery#44129
Helmi wants to merge 3 commits into
NousResearch:mainfrom
Helmi:fix/skill-discovery-symlinks

Conversation

@Helmi

@Helmi Helmi commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

I manage some skills with an external tool that symlinks them from a central vault into each agent's skills folder. They load and run fine — the runtime loader walks with followlinks=True — but everything else sees a different reality: the profile list undercounts skills, usage tracking misses them, curator backups skip them, and the dashboard's skills views don't know they exist. Turns out the loader is the only place that follows symlinks; the other discovery sites all use rglob("SKILL.md"), which never descends into symlinked dirs (on any Python version — 3.13's recurse_symlinks is opt-in).

Fix: swapped every discovery site to the iter_skill_index_files() helper you already have, so there's one canonical answer to "what skills are installed". Nice side effects: the shared exclusion set now applies uniformly (one endpoint scanned without it), and ordering is deterministic everywhere.

Tests: added symlink coverage for iter_skill_index_files() and _count_skills(); the test files for all touched modules pass (448 tests). The full suite shows 45 failures on my machine, but they're identical on a clean checkout of main (auth/systemd/environment-specific) — happy to dig into any of them if they look unexpected on your side.

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard tool/skills Skills system (list, view, manage) labels Jun 11, 2026
@alt-glitch alt-glitch added comp/gateway Gateway runner, session dispatch, delivery comp/dashboard Web dashboard / control panel UI (dashboard/, landing) labels Jun 26, 2026

@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 consolidating discovery around the existing symlink-aware iterator. The premise still holds on current main: hermes_cli/profiles.py:789 uses rglob("SKILL.md"), while agent/skill_utils.py:796 uses os.walk(..., followlinks=True).

Problems

  • The new tests call symlink_to() without an unavailable-symlink skip (tests/agent/test_skill_utils.py:214; tests/hermes_cli/test_profiles.py:1379). Existing skill tests skip OSError/NotImplementedError for this condition at tests/agent/test_skill_commands.py:41-50.
  • Current main has additional discovery paths not covered by this patch: tools/skills_sync.py:84, tools/skill_usage.py:858, and the desktop learning graph at agent/learning_graph.py:80 (introduced by 96552c31e3). Those paths would still miss directory-symlinked skills.

Suggested changes

  • Make the added symlink tests skip when symlinks are unavailable.
  • Reapply the canonical iterator to the remaining current-main discovery paths and add focused coverage for them.

This is an automated hermes-sweeper review.

Comment thread tests/agent/test_skill_utils.py Outdated
real = skills_dir / "real-skill"
real.mkdir()
(real / "SKILL.md").write_text("---\nname: real-skill\n---\n", encoding="utf-8")
(skills_dir / "linked-skill").symlink_to(vault)

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 handle OSError/NotImplementedError and skip when directory symlinks are unavailable. Existing skill symlink tests use this pattern so Windows CI does not fail before reaching the assertion.

@teknium1 teknium1 added 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 14, 2026
@Helmi
Helmi force-pushed the fix/skill-discovery-symlinks branch from 5aba92a to 1573a0b Compare July 21, 2026 15:25
Helmi and others added 3 commits July 31, 2026 09:28
The runtime skill loader walks skills directories with
os.walk(followlinks=True) via iter_skill_index_files(), so symlinked
skill folders load and work. Every other SKILL.md discovery site used
Path.rglob(), which never descends into symlinked directories — so
symlinked skills worked at runtime but were invisible to the profile
skill count, skill usage tracking, the skills hub, curator backups,
profile distribution, dump, the gateway's disabled-skill hint, and the
dashboard's skills endpoints.

Switch all discovery sites to the existing iter_skill_index_files()
helper. This also applies the shared EXCLUDED_SKILL_DIRS pruning
uniformly (one site previously scanned without it) and yields
deterministic ordering everywhere.
… support

Address review feedback: wrap symlink_to in try/except for
OSError/NotImplementedError and pytest.skip, matching the established
pattern in test_image_source, test_skill_manager_tool, and
test_transcription_tools so Windows CI does not fail before reaching
the assertion.
The symlink-aware iterator was applied to most discovery call sites, but
three current-main paths still used a raw rglob and therefore could not
see directory-symlinked skills:

- tools/skills_sync.py — the external skill index, so a vault-linked
  skill was invisible to shadow detection and sync_skills wrote a bundled
  copy over the top of the delegated one
- tools/skill_usage.py — external skill lookup by frontmatter name
- agent/learning_graph.py — the desktop learning graph's own walk

Converts all three to iter_skill_index_files and adds focused coverage
for each. The added symlink tests skip when the platform cannot create
directory symlinks, matching the existing convention in
tests/agent/test_skill_commands.py.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have 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 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.

3 participants