# How ripgrep Handles Symbolic Links During Directory Traversal

> Discover how ripgrep handles symbolic links during directory traversal. Learn about its ignore crate integration, symlink detection, follow flag, and cycle prevention for efficient searching.

- Repository: [Andrew Gallant/ripgrep](https://github.com/BurntSushi/ripgrep)
- Tags: internals
- Published: 2026-03-05

---

**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`](https://github.com/BurntSushi/ripgrep/blob/main/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`](https://github.com/BurntSushi/ripgrep/blob/main/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:

```rust
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`](https://github.com/BurntSushi/ripgrep/blob/main/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`](https://github.com/BurntSushi/ripgrep/blob/main/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:

```rust
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:

```bash

# Symlinked directories are skipped; only real directories are searched

rg "TODO" project/

```

Enable symlink following to traverse linked directories:

```bash

# Follow both file and directory symlinks

rg -L "TODO" project/

```

Root-level symlinks work without flags because you explicitly specified the path:

```bash

# 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/--follow` is supplied, preventing accidental search scope expansion.
- **Cycle safety**: The `check_symlink_loop` function 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.rs`](https://github.com/BurntSushi/ripgrep/blob/main/crates/ignore/src/walk.rs) through the `WalkBuilder` and `Worker` implementations.

## 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.