How ripgrep Handles Symbolic Links During Directory Traversal
ripgrep delegates filesystem walking to the ignore crate, which detects symbolic links via is_symlink(), follows them only when the -L/--follow flag is set, and prevents infinite recursion through ancestry-based cycle detection.
Searching through codebases that contain symbolic links requires careful handling to avoid infinite loops and unexpected traversal behavior. In the BurntSushi/ripgrep repository, all symlink-aware filesystem walking logic is encapsulated within the ignore crate, specifically within crates/ignore/src/walk.rs. The implementation respects user intent through explicit CLI flags while providing automatic safeguards against directory cycles.
Core Traversal Architecture
The filesystem traversal engine centers on the WalkBuilder struct defined in crates/ignore/src/walk.rs. This builder constructs directory walkers that yield DirEntry objects representing filesystem entries. Each entry is wrapped in a Work structure that carries metadata necessary for symlink evaluation. The separation of concerns allows ripgrep's core search engine to remain agnostic about symlink policies while the ignore crate handles the low-level traversal details.
Detecting Symbolic Links
Symlink identification occurs at the entry level through the is_symlink() method on Work instances (lines 72‑75). This method queries the underlying DirEntry for its file type:
fn is_symlink(&self) -> bool {
self.dent.file_type().map_or(false, |ft| ft.is_symlink())
}
The file_type() method originates from crates/ignore/src/types.rs, which provides the low-level metadata abstraction used throughout the traversal pipeline.
The Follow-Links Flag and CLI Control
Traversal behavior is governed by the follow_links: bool field within WalkBuilder (lines 489‑495). By default, this value is false, meaning ripgrep treats symbolic links as opaque objects rather than traversable paths. The CLI option -L or --follow toggles this field to true via the main entry point in src/main.rs, which invokes WalkBuilder::follow_links(true) when the flag is present.
When disabled (the default), directory symlinks are skipped during recursion, though file symlinks may still be matched as regular files if they appear as search results.
Decision Points During Traversal
Inside the worker loop (Worker::run at line 1753), ripgrep evaluates whether to descend into a directory using this conditional logic:
if self.follow_links && is_symlink {
// attempt to follow it
}
This check ensures that symlink following never occurs accidentally. Only when both the user has enabled following and the current entry is identified as a symlink does the traversal logic attempt to enter the target directory.
Preventing Infinite Loops
Before descending into a symlinked directory, ripgrep invokes check_symlink_loop (lines 1892‑1912) to validate the ancestry chain. This function walks the hierarchy of ignore matchers to verify that following the symlink will not create a cycle back to an already-visited directory. If a loop is detected, the walker returns Error::Loop, halting traversal down that branch and preventing the infinite recursion that would otherwise occur in circular symlink structures.
Special Case Handling
Root-Level Symlinks
The helper function walkdir_is_dir (lines 1000‑1014) implements a special exception for root entries. When a user explicitly passes a symlinked directory as a search path (e.g., rg "pattern" linked-dir/), ripgrep treats that symlink as a directory even without the -L flag. This avoids an extra stat call for non-root entries while honoring the user's explicit intent to search that specific path.
File Symlinks
When a symlink points to a regular file rather than a directory, ripgrep handles it as a normal file entry during matching. However, if --follow is disabled and the symlink is not the root path, the symlink itself is ignored rather than traversed as a directory, maintaining consistent behavior with the "no follow" policy.
Practical Usage Examples
Default behavior ignores symlinks during directory traversal:
# Symlinked directories are skipped; only real directories are searched
rg "TODO" project/
Enable symlink following to traverse linked directories:
# Follow both file and directory symlinks
rg -L "TODO" project/
Root-level symlinks work without flags because you explicitly specified the path:
# linked-dir is a symlink, but ripgrep searches it because you named it directly
rg "TODO" linked-dir/
Summary
- Default protection: Symlinks are not traversed unless
-L/--followis supplied, preventing accidental search scope expansion. - Cycle safety: The
check_symlink_loopfunction detects and rejects circular symlink references before descending. - Explicit paths: Root-level symlinked directories are searchable without flags because the user explicitly requested that path.
- Architecture: All logic is centralized in
crates/ignore/src/walk.rsthrough theWalkBuilderandWorkerimplementations.
Frequently Asked Questions
What happens if I search a directory containing symlinks without the -L flag?
Without -L, ripgrep identifies symlinks via is_symlink() and skips them during directory traversal. The symlink itself appears as an entry but its target is not entered, effectively pruning that branch from the search tree. This matches the behavior of find without the -follow option.
Can ripgrep detect and prevent infinite loops caused by circular symlinks?
Yes. Before following any symlink, ripgrep calls check_symlink_loop (lines 1892‑1912) to walk the ancestry chain of the current path. If the symlink target points to a directory already present in the traversal stack, ripgrep returns an Error::Loop and skips that branch, preventing infinite recursion.
Why can I search a symlinked directory without -L if I specify it as the search path?
When you explicitly provide a symlinked directory as a command-line argument (e.g., rg "pattern" symlink/), ripgrep treats this as a root entry. The walkdir_is_dir helper (lines 1000‑1014) recognizes that you intentionally requested that specific path and follows the symlink once to treat it as a directory, regardless of the follow_links flag setting. This exception applies only to paths explicitly named by the user.
Does following symlinks impact search performance?
Following symlinks adds overhead due to the check_symlink_loop validation required for each symlinked directory encountered. However, unless your search space contains extensive symlink networks or circular references, the performance impact is typically negligible compared to the actual file I/O and regex matching operations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →