[BUG] Glob and Grep silently skip symlinked/junction directories during traversal — false "No files found" misleads agent

Resolved 💬 2 comments Opened Jun 11, 2026 by emile-naude Closed Jun 11, 2026

Environment

  • Platform: Anthropic API
  • Claude CLI version: 2.1.173
  • Operating System: Windows 11 Pro (10.0.26200)
  • Terminal: Windows Terminal (PowerShell + Git Bash)

Bug Description

The built-in Glob and Grep tools do not descend into directory reparse points (NTFS symlinks and junctions) encountered while traversing a pattern. Both return No files found — indistinguishable from "the directory doesn't exist or is empty" — even when the link target contains thousands of files.

Point access through the same link works (Read on a full path through the link succeeds, shell ls sees everything), and using the link itself as the search path root also works. Only mid-traversal descent into the link is skipped.

The agent-facing problem is that the failure is a silent false negative. The model has no signal that a link was skipped, so it concludes the files don't exist and reports that to the user. In our workspace (a git repo where one top-level directory is an NTFS symlink to a Google Drive for Desktop G: mount), session transcripts show this in 5 separate sessions over 9 days — each time the agent first concluded a project folder was missing before recovering via shell ls. In one session a repo-rooted Grep for a credential ID returned nothing while the string existed in a file behind the symlink, producing a confidently wrong "this ID isn't anywhere in the repo".

Minimal repro (no admin required — uses a junction)

mkdir C:\repro\real-target
'FINDME-12345' | Set-Content C:\repro\real-target\needle.txt
mkdir C:\repro\repo
New-Item -ItemType Junction -Path C:\repro\repo\junction-dir -Target C:\repro\real-target

Then in a Claude Code session rooted anywhere:

| Tool call | Actual | Expected |
|---|---|---|
| Glob(pattern="junction-dir/*", path="C:\repro\repo") | No files found | needle.txt |
| Glob(pattern="**/*.txt", path="C:\repro\repo") | No files found | needle.txt |
| Grep(pattern="FINDME-12345", path="C:\repro\repo") | No files found | 1 match |
| Glob(pattern="*", path="C:\repro\repo\junction-dir") — link as root | needle.txt ✓ | — |
| Grep(pattern="FINDME-12345", path="C:\repro\repo\junction-dir") — link as root | 1 match ✓ | — |
| Read("C:\repro\repo\junction-dir\needle.txt") | works ✓ | — |

The same behavior occurs with true NTFS symlinks (mklink /D), which is our production case (repo-internal symlink targeting a network/Drive mount).

Expected behavior

Either of these would fix the agent-facing problem:

  1. Follow directory links during traversal (ripgrep --follow / followSymbolicLinks: true semantics), with cycle detection; or
  2. Surface the skip in the tool result — e.g. No files found (1 linked directory was not traversed: clients -> G:\...). Even if links are deliberately not followed (a defensible default), the model could then re-root the search at the link. Today the skip is invisible, so the model cannot know that No files found is unreliable, and the same false conclusion recurs session after session.

Workarounds (for anyone else hitting this)

  • Pass the symlink/junction itself as the path argument — traversal from a link root works.
  • Use shell (ls, find) via the Bash tool, which follows links normally.
  • Per-directory CLAUDE.md note warning the model that the directory is a symlink.

Related

#6936 reported the macOS flavor (files in a symlinked directory not searchable, @ picker) and was closed not-planned. This report adds a no-privilege Windows junction repro, confirms link-as-root works while mid-traversal descent fails, and documents the impact on autonomous agent sessions — which is why option 2 (surfacing the skip) would be valuable even if following links stays off by default.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗