Skip to content

fix(agent): detect symlink cycles in iter_skill_index_files to prevent infinite recursion - #47775

Closed
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:fix/47659-symlink-cycle-detection
Closed

fix(agent): detect symlink cycles in iter_skill_index_files to prevent infinite recursion#47775
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:fix/47659-symlink-cycle-detection

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds symlink cycle detection to iter_skill_index_files() so that self-referencing symlinks inside skill directories (e.g. skills/productivity/productivity -> productivity/) are pruned instead of causing infinite recursion via os.walk(followlinks=True).

Without this guard, self-referencing symlinks inflate the skills prompt from ~1K tokens to ~109K tokens, consuming most of the context window before any conversation starts.

Related Issue

Fixes #47659

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • agent/skill_utils.py: Add a visited set of Path.resolve() real paths to iter_skill_index_files(). When os.walk revisits an already-seen directory (via symlink cycle), prune the branch by setting dirs[:] = [].
  • tests/agent/test_skill_utils.py: Add test_iter_skill_index_files_prunes_self_referencing_symlinks — creates a self-referencing symlink inside a skill directory and verifies each skill appears exactly once (no duplicated entries from the cycle).

How to Test

  1. Create a skill directory with a self-referencing symlink:
    mkdir -p /tmp/test-skills/productivity
    echo -e "---\nname: productivity\n---\n" > /tmp/test-skills/productivity/SKILL.md
    ln -s /tmp/test-skills/productivity /tmp/test-skills/productivity/productivity
  2. Run the test suite: python -m pytest tests/agent/test_skill_utils.py::test_iter_skill_index_files_prunes_self_referencing_symlinks -v
  3. Verify the function returns exactly 2 entries (one per skill), not infinitely recursing.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — fix is pure Python stdlib (pathlib + os.walk), platform-agnostic
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Code Intelligence

  • Analyzed: iter_skill_index_files (callers: 5 in agent/skill_utils.py, agent/prompt_builder.py, tools/skills_tool.py, agent/skill_commands.py)
  • Blast radius: LOW — pure guard addition, no behavioral change for non-cyclic directories
  • Related patterns: os.walk(followlinks=True) cycle detection is a well-known Python pattern; the visited set of resolved paths is the standard approach

…t infinite recursion

Add a visited set of resolved real paths to iter_skill_index_files().
When os.walk(followlinks=True) encounters a symlink cycle (e.g.
skills/productivity/productivity -> productivity/), the branch is
pruned instead of recursing infinitely.  Without this guard, self-
referencing symlinks inflate the skills prompt from ~1K to ~109K
tokens, consuming most of the context window.

Fixes NousResearch#47659
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint tool/skills Skills system (list, view, manage) P3 Low — cosmetic, nice to have duplicate This issue or pull request already exists labels Jun 17, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #18815 — same fix (add a visited set of Path.resolve() real paths to iter_skill_index_files() and prune revisited dirs via dirs[:] = []) for the symlink-cycle infinite-recursion in os.walk(..., followlinks=True). #18815 (2026-05-02) is the earliest open PR with this exact mechanism; #20658 also competes with an identical approach. Verified the bug is still live on main (the current dirs[:] filter only excludes metadata/support dirs, no cycle guard). Fixes the same issue as #47659#18809.

@liuhao1024

Copy link
Copy Markdown
Contributor Author

Closing as self-duplicate of #18815, which covers the same fix (visited set for iter_skill_index_files symlink cycle detection) with more comprehensive tests and was opened earlier. #18815 is the canonical PR.

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

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have 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.

build_skills_system_prompt: self-referencing symlinks cause infinite recursion in os.walk(followlinks=True)

2 participants