Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions agent/skill_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,14 @@ def iter_skill_index_files(skills_dir: Path, filename: str):
"""
skills_dir_str = str(skills_dir)
matches: list[str] = []
visited_dirs: set[str] = set()
for root, dirs, files in os.walk(skills_dir_str, followlinks=True):
real_root = os.path.realpath(root)
if real_root in visited_dirs:
dirs[:] = []
continue
visited_dirs.add(real_root)

has_skill_md = "SKILL.md" in files
dirs[:] = [
d
Expand Down
20 changes: 20 additions & 0 deletions tests/agent/test_skill_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,26 @@ def test_iter_skill_index_files_prunes_dependency_dirs(tmp_path):
assert found == [real / "SKILL.md"]


def test_iter_skill_index_files_prevents_symlink_loops(tmp_path):
"""Circular symlinks pointing back to parent/ancestor dirs should not cause infinite recursion."""
import pytest

real = tmp_path / "skill-a"
real.mkdir()
(real / "SKILL.md").write_text("---\nname: skill-a\n---\n", encoding="utf-8")

# Create a self-referential symlink inside real pointing back to real
loop = real / "self_link"
try:
loop.symlink_to(real, target_is_directory=True)
except OSError:
pytest.skip("Symlinks not supported")

found = list(iter_skill_index_files(tmp_path, "SKILL.md"))
assert found == [real / "SKILL.md"]



def test_skill_config_helpers_share_raw_config_parse_cache(tmp_path, monkeypatch):
"""Repeated skill config helpers should parse config.yaml only once."""
from agent import skill_utils
Expand Down