Title
Support generating subdirectory ruff.toml files with path-adjusted per-file-ignores
Problem
Ruff supports hierarchical configuration via extend, which is useful for having different linter thresholds in subdirectories (e.g., relaxed max-args in tests/). However, per-file-ignores patterns inherited via extend break because ruff resolves glob patterns relative to the config file's directory, not the original source.
Example
project/
├── pyproject.toml # root config
│ [tool.ruff.lint.per-file-ignores]
│ "tests/**/*.py" = ["S101", "TC001", "TC002"]
│
└── tests/
└── ruff.toml # extends root, overrides max-args
extend = "../pyproject.toml"
[lint.pylint]
max-args = 10
When ruff processes tests/ruff.toml, the inherited "tests/**/*.py" pattern resolves to tests/tests/**/*.py — matching nothing. The TC001/TC002 ignores silently stop applying, which changes isort's import classification and cascades into I001 sorting violations across every existing test file.
Why extend-per-file-ignores doesn't fully solve it
Ruff shipped extend-per-file-ignores astral-sh/ruff#2558 which allows additive ignores. You can move the test ignores into tests/ruff.toml with "**/*.py" patterns. However, the TC001/TC002 ignores affect isort's import classification — adding them from a different config file changes the expected sort order for all existing test files, requiring a mass reformat of unrelated files.
The net result: there's no way to use a subdirectory ruff.toml for threshold overrides without either breaking per-file-ignores inheritance or causing import sorting churn across the entire test suite.
How ruff-sync could help
ruff-sync already handles merging upstream configs into local projects with path awareness. A natural extension would be generating subdirectory config files (like tests/ruff.toml) from a root config, with glob patterns automatically adjusted for the target directory.
Potential approaches
Option A: Path-rewriting on sync
When syncing a root config into a subdirectory ruff.toml, rewrite per-file-ignores patterns to be relative to the target directory. For example, "tests/**/*.py" in the root becomes "**/*.py" in tests/ruff.toml.
This could be a new command or flag:
# Generate tests/ruff.toml from root config with path-adjusted patterns
ruff-sync subdirectory tests/ --overrides tests/ruff-overrides.toml
Option B: Subdirectory config generation
A dedicated feature for generating subdirectory configs that:
- Takes the root config as the base
- Applies user-specified overrides (threshold changes, additional ignores)
- Rewrites all glob patterns to be relative to the target directory
- Outputs a self-contained
ruff.toml that doesn't need extend
This avoids the extend path resolution bug entirely by producing a standalone config.
Option C: Exclude + extend-per-file-ignores coordination
When syncing, detect per-file-ignores patterns that reference the target subdirectory and automatically:
- Exclude them from the parent sync (so they don't break via
extend)
- Add them to the child's
extend-per-file-ignores with adjusted paths
Context
This came up while trying to set different pylint.max-args and mccabe.max-complexity thresholds for test files without disabling the rules entirely via per-file-ignores. The tests/ruff.toml approach is the documented way to do this, but it's broken in practice for any project that uses TC001/TC002 ignores on test files.
Related issues
Title
Support generating subdirectory ruff.toml files with path-adjusted per-file-ignores
Problem
Ruff supports hierarchical configuration via
extend, which is useful for having different linter thresholds in subdirectories (e.g., relaxedmax-argsintests/). However,per-file-ignorespatterns inherited viaextendbreak because ruff resolves glob patterns relative to the config file's directory, not the original source.Example
When ruff processes
tests/ruff.toml, the inherited"tests/**/*.py"pattern resolves totests/tests/**/*.py— matching nothing. The TC001/TC002 ignores silently stop applying, which changes isort's import classification and cascades into I001 sorting violations across every existing test file.Why
extend-per-file-ignoresdoesn't fully solve itRuff shipped
extend-per-file-ignoresastral-sh/ruff#2558 which allows additive ignores. You can move the test ignores intotests/ruff.tomlwith"**/*.py"patterns. However, the TC001/TC002 ignores affect isort's import classification — adding them from a different config file changes the expected sort order for all existing test files, requiring a mass reformat of unrelated files.The net result: there's no way to use a subdirectory
ruff.tomlfor threshold overrides without either breakingper-file-ignoresinheritance or causing import sorting churn across the entire test suite.How ruff-sync could help
ruff-sync already handles merging upstream configs into local projects with path awareness. A natural extension would be generating subdirectory config files (like
tests/ruff.toml) from a root config, with glob patterns automatically adjusted for the target directory.Potential approaches
Option A: Path-rewriting on sync
When syncing a root config into a subdirectory
ruff.toml, rewriteper-file-ignorespatterns to be relative to the target directory. For example,"tests/**/*.py"in the root becomes"**/*.py"intests/ruff.toml.This could be a new command or flag:
# Generate tests/ruff.toml from root config with path-adjusted patterns ruff-sync subdirectory tests/ --overrides tests/ruff-overrides.tomlOption B: Subdirectory config generation
A dedicated feature for generating subdirectory configs that:
ruff.tomlthat doesn't needextendThis avoids the
extendpath resolution bug entirely by producing a standalone config.Option C: Exclude + extend-per-file-ignores coordination
When syncing, detect
per-file-ignorespatterns that reference the target subdirectory and automatically:extend)extend-per-file-ignoreswith adjusted pathsContext
This came up while trying to set different
pylint.max-argsandmccabe.max-complexitythresholds for test files without disabling the rules entirely viaper-file-ignores. Thetests/ruff.tomlapproach is the documented way to do this, but it's broken in practice for any project that uses TC001/TC002 ignores on test files.Related issues
per-file-ignorespath resolution breaks withextendacross directories (open)extend-per-file-ignoresfeature request (closed, shipped)