fix(skills): symlink-aware, cycle-safe Hermes skill discovery - #151
Merged
Conversation
HermesSkillSource resolved SKILL.md via Path.rglob('SKILL.md') at three
sites. rglob refuses to descend into symlinked directories on Python <3.13
(the recurse_symlinks kwarg is 3.13-only), so a Hermes layout that symlinks
user-installed skills into the framework tree silently resolved 'not found'.
Replace all three sites with one private _iter_skill_files(root) helper:
os.walk(followlinks=True) + a (st_dev, st_ino) visited-set cycle guard
(prunes a symlink pointing back to an ancestor -> provably terminating) +
per-level sort() for deterministic first-match-wins. onerror/stat failures
are debug-logged and skipped (parity with the old silent skip). find_skill
walks once and runs both passes over the materialized list.
Scope held to HermesSkillSource: the flat ClaudeCode/LocalDir sources
already follow symlinks via is_file()/iterdir() (noted inline).
9 new symlink tests (correctness C1-C5, cycle/permission/dangling safety
S1-S3, determinism D1) with an os.symlink-unavailable skip-guard; the
symlinked-directory cases were red on the old rglob code. Full non-slow
suite green (1763 passed, +9), ruff clean.
Resolves the symlink item of NousResearch#142;
the other five #142 fixes are already-covered clusters. Marks the triage
action item DONE (row + review-log + snapshot delta).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves the symlink item of upstream
NousResearch/hermes-agent-self-evolution#142(the one genuinely-new find from the 2026-07-06 triage sweep; the other five #142 fixes are already-covered clusters).Problem
HermesSkillSourcediscoveredSKILL.mdviaPath.rglob("SKILL.md")at three sites.rglobrefuses to descend into symlinked directories on Python <3.13 (therecurse_symlinkskwarg is 3.13-only). The standard Hermes layout symlinks user-installed skills into the framework tree, so those skills silently resolved "not found." Reproduced red on 3.13: the symlinked-directory discovery tests failed, while the symlinked-file case already worked (rglob matches symlinked files).Fix (scoped to
evolution/core/skill_sources.py)One private helper
_iter_skill_files(root)replaces all threerglobsites:os.walk(followlinks=True)— portable across 3.10–3.13 (norecurse_symlinks).(st_dev, st_ino)visited-set prunes a symlink pointing back to an ancestor → provably terminating (os.walk(followlinks=True)alone loops forever on a cycle).sort()→ stable first-match-wins.onerror/statfailures are debug-logged and skipped, matching the old silent skip.find_skillwalks once, runs both passes over the materialized list.Blast radius: only
HermesSkillSource. The flatClaudeCodeSkillSource/LocalDirSkillSourcealready follow symlinks viais_file()/iterdir()— left untouched, noted inline. No public API change.Tests —
tests/core/test_skill_sources.py(+9)Real
os.symlinktemp trees with a skip-guard where symlinks aren't creatable:SKILL.mdfile, frontmatter-fallback across a symlink, mixed real+symlink listing.followlinks), unreadable dir skipped, dangling symlink no-raise.The symlinked-directory cases were red on the old
rglobcode; all green after.Verification
uv run pytest tests/ -q -m 'not slow'→ 1763 passed (+9), 0 failures.ruff checkclean.Marks the #142 (partial) triage action item DONE (row + review-log entry + snapshot delta, in this PR).