astral-sh/ruff · error · anyhow::Error

Expected at least one path to search for Python files

Error message

Expected at least one path to search for Python files

What it means

Guard inside `project_files_in_path` (crates/ruff_workspace/src/resolver.rs:429), the shared entry point ruff and ty use to walk paths for Python files. After normalizing and deduplicating the input slice, the function needs a first path to seed `ignore::WalkBuilder`; an empty slice violates that invariant and is converted into this anyhow error. Note that the `force-exclude` branch just above returns `Ok((vec![], resolver))` early when exclusions remove everything, so only a caller that passes zero paths up front can actually reach it.

Source

Thrown at crates/ruff_workspace/src/resolver.rs:478

                } else {
                    // We already visited this ancestor, we can stop here.
                    break;
                }
            }
        }
    }

    // Check if the paths themselves are excluded.
    if resolver.force_exclude() {
        paths.retain(|path| !is_file_excluded(path, &resolver));
        if paths.is_empty() {
            return Ok((vec![], resolver));
        }
    }

    let (first_path, rest_paths) = paths
        .split_first()
        .ok_or_else(|| anyhow!("Expected at least one path to search for Python files"))?;
    // Create the `WalkBuilder`.
    let mut builder = WalkBuilder::new(first_path);

    if let Ok(cwd) = std::env::current_dir() {
        builder.current_dir(cwd);
    }

    for path in rest_paths {
        builder.add(path);
    }
    builder.standard_filters(resolver.respect_gitignore());
    builder.hidden(false);

    builder.threads(
        std::thread::available_parallelism()
            .map_or(1, std::num::NonZeroUsize::get)
            .min(12),
    );

View on GitHub (pinned to 672bb4edf0)

Solutions

  1. Default the path list to the current directory when your collection step yields nothing: `if paths.is_empty() { paths.push(std::env::current_dir()?); }`
  2. Log `paths.len()` immediately before the call to find which upstream filter emptied it and fix that filter
  3. If you only need CLI behavior, shell out to `ruff check .` / `ty check .` instead of calling the function directly

Example fix

// before
let files = project_files_in_path(&paths, &pyproject_config, &transformer)?; // paths == vec![]

// after
let paths = if paths.is_empty() {
    vec![std::env::current_dir()?]
} else {
    paths
};
let files = project_files_in_path(&paths, &pyproject_config, &transformer)?;
Defensive patterns

Strategy: validation

Validate before calling

if paths.is_empty() {
    anyhow::bail!("no paths to search: refusing to call project_files_in_path");
}

Try / catch

let result = project_files_in_path(&paths, &cfg, &transformer);
if let Err(e) = &result {
    if e.to_string().contains("Expected at least one path") {
        // caller bug: fix the empty input; do not retry
    }
}

Prevention

When it happens

Trigger: Calling `ruff_workspace::resolver::project_files_in_path(&[], &pyproject_config, &transformer)` with an empty path slice — typically because an upstream glob or filter removed every entry before the call. The bundled CLIs always default to `.`, so end users of `ruff`/`ty` effectively never see this.

Common situations: Editor integrations or custom runners built on ruff_workspace that compute the file set themselves (empty workspace, empty glob result, or all candidate paths pre-filtered out) and forward the empty Vec unchanged.

Related errors


AI-assisted analysis of astral-sh/ruff@672bb4edf0 (2026-08-16). Data as JSON: /api/errors/f719c6ee4b2b0a03. Report an issue: GitHub.