Skip to content

Nondeterminism in ignore::WalkBuilder parallel multi-root walk #3419

@jelle-openai

Description

@jelle-openai

Please tick this box to confirm you have reviewed the above.

  • I have a different issue.

What version of ripgrep are you using?

14.1.1

How did you install ripgrep?

Cargo

What operating system are you using ripgrep on?

macOS 26.5

Describe your bug.

ignore::WalkBuilder appears to produce nondeterministic results for a parallel multi-root walk when one root has a scoped ignore rule that should not apply to another root.

What are the steps to reproduce the behavior?

Minimal layout:

.git/
.gitignore
noxfile.py
src/scikit_build_core/build/metadata.py
tests/test_metadata.py

.gitignore:

tests/**/build/

The source build file should always be included: the ignore rule only matches build/ directories below tests/.

Minimal Rust repro with ignore = "0.4.25":

use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};

fn source_build_file_was_seen(root: &Path) -> bool {
    let source_build_file = root.join("src/scikit_build_core/build/metadata.py");
    let seen = AtomicBool::new(false);

    let mut builder = ignore::WalkBuilder::new(root.join("src"));
    builder.current_dir(root);
    builder.standard_filters(true);
    builder.add(root.join("tests"));
    builder.add(root.join("noxfile.py"));
    builder.threads(12);

    builder.build_parallel().run(|| {
        Box::new(|entry| {
            if let Ok(entry) = entry {
                if entry.path() == source_build_file {
                    seen.store(true, Ordering::Relaxed);
                }
            }

            ignore::WalkState::Continue
        })
    });

    seen.load(Ordering::Relaxed)
}

fn main() {
    let root = Path::new(env!("CARGO_MANIFEST_DIR"));
    let mut seen = 0;
    let mut missing = 0;

    for _ in 0..100 {
        if source_build_file_was_seen(root) {
            seen += 1;
        } else {
            missing += 1;
        }
    }

    println!("seen={seen} missing={missing}");
}

Observed output on my machine:

seen=14 missing=86

The same issue reproduces via ripgrep:

rg --threads 12 --files src tests noxfile.py

Across 100 identical runs, src/scikit_build_core/build/metadata.py was present 59 times and missing 41 times.

Control cases were stable:

rg --threads 1 --files src tests noxfile.py
# source file present 100/100

rg --threads 12 --files src
# source file present 100/100

So this looks specific to the parallel multi-root walk. The expected behavior is that tests/**/build/ never excludes src/scikit_build_core/build/metadata.py, regardless of walk scheduling.

This is causing nondeterminism in ty too: it makes it so some files in the scikit-build-core project are sometimes checked and sometimes ignored by ty. The repro case in this issue is a simplified version of the shape in scikit_build_core.

What is the actual behavior?

Inconsistent output from rg --threads 12 --files src tests noxfile.py.

What is the expected behavior?

Rules should be applied consistently

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions