fix(agent): track visited realpaths in iter_skill_index_files to prevent symlink loops - #69248
Conversation
…ent symlink loops
There was a problem hiding this comment.
Pull request overview
This PR hardens skill-directory scanning used during system-prompt construction by preventing os.walk(..., followlinks=True) from recursing indefinitely when encountering circular/self-referential symlinks.
Changes:
- Add realpath-based loop detection in
iter_skill_index_files()to stop revisiting the same directory via symlinks. - Add a regression test that creates a self-referential symlink and asserts traversal terminates and returns the expected
SKILL.md.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
agent/skill_utils.py |
Tracks visited directory realpaths during skill index traversal to prune recursion on repeats. |
tests/agent/test_skill_utils.py |
Adds a unit test covering circular symlink traversal behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| visited_dirs.add(real_root) | ||
|
|
||
| has_skill_md = "SKILL.md" in files | ||
| dirs[:] = [ |
Duplicate of #18815: the current live diff implements the same |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing the real symlink-loop risk. The iter_skill_index_files() guard is sound for that helper, but the current prompt-build path needs the same protection.
Problems
agent/prompt_builder.py:1350independently callsos.walk(..., followlinks=True)without a visited-realpath guard.build_skills_system_prompt()reaches it during snapshot validation through_load_skills_snapshot()(agent/prompt_builder.py:1566,agent/prompt_builder.py:1383) and during cold snapshot creation (agent/prompt_builder.py:1633-1636), so a cyclic skill tree can still block prompt construction.- The added test covers only
iter_skill_index_files(); it does not cover either prompt snapshot path.
Suggested changes
- Apply a shared cycle-safe traversal strategy to both walkers while retaining non-cyclic symlink discovery.
- Add
build_skills_system_prompt()regressions for cold snapshot creation and existing-snapshot validation.
Automated hermes-sweeper review.
| dirs[:] = [] | ||
| continue | ||
| visited_dirs.add(real_root) | ||
|
|
There was a problem hiding this comment.
This guard fixes the shared iterator, but the same cyclic tree still reaches the unguarded manifest walk in agent/prompt_builder.py:1350 during snapshot validation and cold snapshot writes. Please apply the same cycle-safe traversal strategy there and add prompt-build coverage.
SummaryFive PRs address the shared symlink-cycle cause reported in #18809 and #47659. Each diff adds realpath-based revisit detection to Related pull requests
Duplicates#20658, #47775, #69244, and #69248 duplicate #18815's realpath-based cycle guard for Suggested consolidationKeep #18815 open with a salvage path as the recorded best existing fix: retain its iterator guard and tests, split the unrelated Complex graphflowchart TD
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I18809(["issue #18809 (open)"])
I47659(["issue #47659 (open)"])
subgraph Dup18815 ["PRs duplicating each other"]
P18815["PR #18815 (open)"]
P20658["PR #20658 (open)"]
P47775["PR #47775 (closed)"]
P69244["PR #69244 (closed)"]
P69248["PR #69248 (open)"]
end
P69248 -->|fixes| I18809
P69248 -.->|partial| I47659
class I18809 open
class I47659 open
class P18815 open
class P20658 open
class P47775 closed
class P69244 closed
class P69248 open
class P18815 best
class P18815 best
class P69248 target
click I18809 "https://github.com/NousResearch/hermes-agent/issues/18809"
click I47659 "https://github.com/NousResearch/hermes-agent/issues/47659"
click P18815 "https://github.com/NousResearch/hermes-agent/pull/18815"
click P20658 "https://github.com/NousResearch/hermes-agent/pull/20658"
click P47775 "https://github.com/NousResearch/hermes-agent/pull/47775"
click P69244 "https://github.com/NousResearch/hermes-agent/pull/69244"
click P69248 "https://github.com/NousResearch/hermes-agent/pull/69248"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 5 pull requests and 2 issues in this complex. Each diff was read against this issue; Assessment working set: 18 kB of PR diffs, 14 kB of issue/PR text, 8 kB of discussion (15 comments), 15 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
Summary
Hardens skill-directory scanning during system-prompt construction by preventing
os.walk(..., followlinks=True)from recursing indefinitely when encountering circular/self-referential symlinks.Changes
iter_skill_index_files()traversal and prune recursion when a realpath repeats.tests/agent/test_skill_utils.py.