Skip to content

exclude is resolved relative to cwd, not config dir, with --config #26453

Description

@alchemmist

Summary

Summary

With an explicit --config <path>, exclude / extend-exclude patterns are
matched against the target path relative to the current working directory,
not relative to the configuration file's directory. So the same config excludes
a directory when ruff runs from the project root, but formats files inside
that excluded directory
when ruff runs from a subdirectory. A leading **/
does not help — the parent path components are absent from the cwd-relative path.

This breaks editors and build tools that invoke ruff from a subdirectory with an
explicit config.

This is the same root cause as #12058, but for exclude/extend-exclude rather
than per-file-ignores.

Reproduction

docker run --rm alchemmist/ruff-exclude-repro

It builds the tree, prints the config and the exact commands, and shows ruff's
own diff output proving the leak, for ruff 0.14.1 and 0.15.19.

Manual reproduction & full details

ruff.toml at the project root:

force-exclude = true
extend-exclude = ["**/libra/tests/py2_legacy/**"]

Tree (both files badly formatted, so ruff format wants to rewrite them):

user_sessions/
  ruff.toml
  libra/src/good.py                  # normal, should be formatted
  libra/tests/py2_legacy/legacy.py   # python2 legacy, should be EXCLUDED
# from the project root — correct: only good.py is touched
$ cd user_sessions && ruff format . --config ruff.toml --diff
--- libra/src/good.py
1 file would be reformatted

# from a subdirectory — WRONG: the excluded file is reformatted too
$ cd user_sessions/libra && ruff format . --config ../ruff.toml --diff
--- src/good.py
--- tests/py2_legacy/legacy.py    # <-- excluded file leaks
2 files would be reformatted

Expected: the excluded directory is excluded regardless of cwd — ideally
exclude/extend-exclude anchored to the config file's directory.

Actual: the exclude is anchored to the cwd; from a subdirectory, patterns
with parent path components silently stop matching and excluded files get
formatted. Only a bare leaf basename (py2_legacy, no slash) is stable, but it
matches such a directory anywhere and cannot target a specific path.

Note: with config auto-discovery (no --config) ruff anchors excludes to
the discovered config directory and the bug does not appear; passing
--config <path> is what triggers it.

Versions: reproduced on ruff 0.14.1 and 0.15.19 (native Linux, amd64 + arm64).

Related

Version

0.14.1 & 0.15.19

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions