Fix: Ruff Not Working — Configuration Errors, Rule Selection, and Format vs Lint Confusion
Quick Answer
How to fix Ruff errors — pyproject.toml configuration not applied, rule code unknown, ruff format vs ruff check confusion, ignore not working, per-file-ignores, line-length conflicts, and migrating from Flake8 Black isort.
The Error
You configure Ruff and the rules don’t apply:
# pyproject.toml
[tool.ruff]
select = ["E", "W", "F"]ruff check .
# Still flags issues you tried to ignoreOr you get a confusing error about unknown rule codes:
ruff failed
Cause: Unknown rule code: `E5`Or # noqa comments are silently ignored:
result = some_function(x) # noqa: F841
# Ruff still reports F841Or you can’t tell whether to use ruff check or ruff format:
ruff .
# error: unrecognized subcommand 'check' (which is not the same as `ruff check`)Or the formatter and linter disagree about line length:
E501 Line too long (89 > 88)
# But Ruff format produced this exact lineRuff is a Rust-based replacement for Flake8, isort, and several other Python tools. It’s 10–100x faster than its predecessors, but the configuration system is its own — copying a Flake8 config doesn’t work, and the rule codes (E501, F401, I001) come from the original tools but are reorganized. This guide covers the most common failure modes.
Why This Happens
Ruff implements rules from many existing tools (pycodestyle, pyflakes, isort, pyupgrade, etc.) but unifies them under a single CLI and configuration. The rule codes preserve their original prefixes (E for pycodestyle, F for pyflakes, I for isort) which makes migration easier — but means understanding the rule namespace requires knowing where each rule came from.
The ruff check (linter) and ruff format (formatter) are separate commands with separate configurations. They share some settings (like line length) but apply different rules. Mixing them up is the most common confusion: ruff format doesn’t run linting; ruff check doesn’t reformat code unless you pass --fix.
Fix 1: ruff check vs ruff format — They’re Different Commands
ruff check runs the linter — finds issues, reports them, optionally fixes some:
ruff check . # Lint, report only
ruff check --fix . # Lint and auto-fix safe issues
ruff check --fix --unsafe-fixes . # Include fixes that may change behaviorruff format runs the formatter — reformats code (whitespace, quotes, line length):
ruff format . # Reformat all files
ruff format --check . # Check if files are formatted (CI mode)
ruff format --diff . # Show what would changeRun both in CI:
ruff check . # Fail on lint issues
ruff format --check . # Fail if files aren't formattedCommon workflow — format then lint:
ruff format . # Apply formatting changes
ruff check --fix . # Auto-fix lint issues
ruff check . # Verify nothing remainsPro Tip: ruff check --fix and ruff format operate on different layers. The formatter handles whitespace, quotes, and line wrapping (like Black). The linter handles import sorting, unused imports, and code smell rules. Run formatter first, then linter — fixing lint issues sometimes changes line lengths, which the formatter would then need to re-wrap.
Fix 2: pyproject.toml Configuration Not Applied
Configuration changes don’t seem to take effect. Common causes:
Cause 1: Wrong section name. Ruff uses [tool.ruff] for general settings, with sub-sections for the linter and formatter:
# pyproject.toml
# Top-level settings (apply to both check and format)
[tool.ruff]
line-length = 100
target-version = "py312"
# Linter-specific settings
[tool.ruff.lint]
select = ["E", "W", "F", "I", "UP", "B"]
ignore = ["E501"]
# Formatter-specific settings
[tool.ruff.format]
quote-style = "double"
indent-style = "space"Common Mistake: Putting select and ignore directly under [tool.ruff] instead of [tool.ruff.lint]. This was valid in Ruff 0.0.x but moved to [tool.ruff.lint] in 0.2+. The old form prints a deprecation warning and may stop working in future versions.
# OLD — deprecated
[tool.ruff]
select = ["E", "F"] # Warning: should be in [tool.ruff.lint]
# NEW — correct
[tool.ruff.lint]
select = ["E", "F"]Cause 2: Wrong config file location. Ruff looks for config in this order:
pyproject.tomlin the file’s directorypyproject.tomlwalking up the directory treeruff.toml(no[tool.ruff]prefix needed).ruff.toml(same asruff.toml)
# ruff.toml (note: no [tool.ruff] prefix)
line-length = 100
[lint]
select = ["E", "F", "I"]
[format]
quote-style = "double"Verify which config Ruff is using:
ruff check --show-settings .
# Prints the resolved configuration including which file it came fromFix 3: Rule Selection — Understanding the Codes
ruff failed
Cause: Unknown rule code: `E5`Rule codes are exact: E501, not E5. The prefix indicates the source:
| Prefix | Source | Examples |
|---|---|---|
E, W | pycodestyle | E501 (line too long), W292 (no newline at EOF) |
F | pyflakes | F401 (unused import), F841 (unused variable) |
I | isort | I001 (import block sorting) |
UP | pyupgrade | UP007 (use X | Y instead of Optional[X]) |
B | flake8-bugbear | B008 (mutable default argument) |
N | pep8-naming | N802 (function name should be lowercase) |
D | pydocstyle | D100 (missing docstring) |
S | flake8-bandit | S101 (use of assert) |
RUF | Ruff-specific | RUF001 (ambiguous unicode) |
Select rule categories instead of individual codes:
[tool.ruff.lint]
select = [
"E", # All pycodestyle errors
"W", # All pycodestyle warnings
"F", # All pyflakes
"I", # All isort
"UP", # All pyupgrade
"B", # All flake8-bugbear
"C4", # All flake8-comprehensions
"SIM", # All flake8-simplify
]Browse all available rules — ruff rule shows details:
ruff rule E501
# E501: Line too long
# Description, examples, and references
ruff linter
# List all rule sources (linters Ruff implements)
ruff check --select E --statistics .
# Statistics on which rules triggeredSelect everything except specific rules:
[tool.ruff.lint]
select = ["ALL"] # Enable every rule
ignore = ["D", "ANN", "FA"] # Disable docstring, annotation, future-annotations rulesALL is rarely the right choice for a codebase — it includes contradictory rules and many extremely strict rules. Start narrower and expand.
Fix 4: # noqa Comments Not Working
result = some_function(x) # noqa: F841
# Ruff still reports F841Cause 1: Wrong rule code in noqa. The code must match the actual rule. Use noqa without a code to ignore everything on that line (not recommended):
# Disable specific rules
import os # noqa: F401
# Multiple rules
import os, sys # noqa: F401, E401
# All rules on this line (lazy, avoid)
import os # noqaCause 2: The rule isn’t in your select. If F841 isn’t enabled, the noqa comment is unused — Ruff may flag it via RUF100:
[tool.ruff.lint]
select = ["E", "F"] # F841 is in 'F', so noqa works
# RUF100 catches unused noqa directives
extend-select = ["RUF100"]Cause 3: Auto-fix removed the line entirely. If --fix removes an unused import, the noqa comment goes with it — the rule still fires on the next run because the import was reintroduced.
File-level disable:
# At the top of the file — disable specific rules for the whole file
# ruff: noqa: F401, F811
import unused_moduleDisable all rules in a block:
# fmt: off ← Disables formatter
x = [1,2,3] ← Stays as-is, no formatting applied
# fmt: on
# ruff: noqa ← Disables linter for the rest of the fileFix 5: Per-File and Per-Directory Configuration
Different rules for tests, scripts, or generated code:
[tool.ruff.lint.per-file-ignores]
# Tests can use assert (S101) and have unused fixtures
"tests/**/*.py" = ["S101", "F841"]
# CLI scripts can have print statements (T201) and not require docstrings (D)
"scripts/**/*.py" = ["T201", "D"]
# __init__.py needs unused imports for re-exports
"__init__.py" = ["F401"]
# Migrations are auto-generated
"migrations/**/*.py" = ["ALL"]Different settings per directory with separate config files:
project/
├── pyproject.toml ← Default config for the whole project
├── src/
│ └── ...
└── notebooks/
├── ruff.toml ← Lenient config for notebooks
└── analysis.py# notebooks/ruff.toml
extend = "../pyproject.toml" # Inherit from parent config
[lint]
ignore = ["E501", "T201"] # Allow long lines and print statementsPre-commit integration:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-formatFix 6: Migrating from Flake8, Black, and isort
Ruff replaces all three. The migration is straightforward but requires understanding what changes.
Old setup:
# pyproject.toml
[tool.black]
line-length = 100
target-version = ["py312"]
[tool.isort]
profile = "black"
line_length = 100
# .flake8 (separate file because Flake8 doesn't read pyproject.toml)
[flake8]
max-line-length = 100
ignore = E501,W503New Ruff setup:
# pyproject.toml — Ruff replaces all three
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.ruff.lint]
select = [
"E", "W", "F", # Pycodestyle + pyflakes (Flake8 equivalent)
"I", # isort
"UP", # pyupgrade
]
ignore = ["E501"]
[tool.ruff.format]
quote-style = "double" # Black uses double quotesRun the same way:
# Replace these three commands
black .
isort .
flake8 .
# With Ruff (much faster)
ruff format .
ruff check --fix .profile = "black" for isort compatibility:
Ruff’s isort implementation uses Black-compatible defaults by default — no config needed for the common case. If you had non-default isort settings, port them:
[tool.ruff.lint.isort]
known-first-party = ["my_package"]
known-third-party = ["pandas", "numpy"]
combine-as-imports = true
force-single-line = false
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]Fix 7: Line Length Conflicts Between Formatter and Linter
E501 Line too long (89 > 88)But you just ran ruff format and the line is still 89 characters. Why?
The formatter doesn’t always wrap lines — strings, comments, and certain expressions can exceed line-length. The linter still flags them with E501.
Solution 1: Disable E501 if you trust the formatter:
[tool.ruff.lint]
ignore = ["E501"] # Let the formatter decide line lengthsSolution 2: Use the same line length for both:
[tool.ruff]
line-length = 100 # Applies to both formatter and linterSolution 3: Allow long lines in specific cases:
# Long URL in a comment — can't be wrapped
# https://docs.example.com/very/long/path/to/some/resource?with=many&query=parameters # noqa: E501
# Long string that shouldn't be split
url = "https://docs.example.com/very/long/path/to/some/resource" # noqa: E501The Black/Ruff philosophy: Lines should usually fit, but the formatter doesn’t aggressively wrap things that would hurt readability. Let the formatter make the call by ignoring E501 entirely.
Fix 8: Auto-Fix and Unsafe Fixes
ruff check --fix . # Apply only "safe" fixes
ruff check --fix --unsafe-fixes . # Include fixes that may change behaviorSafe fixes are guaranteed to preserve behavior — removing unused imports, sorting imports, fixing whitespace.
Unsafe fixes may change semantics:
UP007:Optional[X]→X | None(changes runtime type if libraries inspect annotations)B006: Mutable default argument fix may change function behaviorF841: Auto-removing unused variables may break debugger watch expressions
Preview what will change:
ruff check --fix --diff . # Show diff without applying
ruff check --fix --unsafe-fixes --diff .Disable auto-fix for specific rules:
[tool.ruff.lint]
select = ["ALL"]
unfixable = ["F841", "ERA001"] # Never auto-fix these even with --fixThis lets the rule still fire (you see the issue) without Ruff silently rewriting your code.
Still Not Working?
Ruff with Pre-commit Hooks
If pre-commit doesn’t run Ruff or runs an outdated version, update the hook revision:
pre-commit autoupdate# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.0 # Pin to specific version
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix] # Fail commit if anything was fixed
- id: ruff-formatEditor Integration — VS Code
Install the official Ruff extension. Configure in .vscode/settings.json:
{
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports.ruff": "explicit"
}
}
}For VS Code Python extension setup issues, see VSCode ESLint not working for similar editor integration patterns.
Performance — Ruff Should Be Fast
If Ruff is slow (more than a few seconds on a large codebase), check:
- Excessive
select = ["ALL"]— enables hundreds of rules including slow ones (D, ANN) - Large
excludelists with complex patterns - Running Ruff via Python — the
ruffCLI is a Rust binary; running throughpython -m ruffadds Python startup overhead
# Measure
time ruff check .
# Should be under 1s for thousands of filesInstallation Issues
If pip install ruff fails with build errors on unusual platforms, try the binary install via uv or pipx:
uv tool install ruff
# or
pipx install ruffBoth manage isolated environments and put the ruff binary on your PATH. For general Python packaging issues that affect tool installation, see Python packaging not working.
Type Checking with mypy
Ruff doesn’t do type checking — it’s a linter and formatter. For type checking, use mypy or pyright. For mypy errors that often appear alongside Ruff configuration issues, see Python mypy type error.
Ruff Formatter vs Black
Ruff format is Black-compatible by default — same line length, same quote style, same major formatting decisions. There are minor differences (e.g., Ruff handles certain edge cases differently), but the output is functionally equivalent for almost all code. Switching from Black to Ruff format produces minimal diffs.
For pre-commit hook setup that affects Git workflow, see git hooks not running.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Apache Airflow Not Working — DAG Not Found, Task Failures, and Scheduler Issues
How to fix Apache Airflow errors — DAG not appearing in UI, ImportError preventing DAG load, task stuck in running or queued, scheduler not scheduling, XCom too large, connection not found, and database migration errors.
Fix: BeautifulSoup Not Working — Parser Errors, Encoding Issues, and find_all Returns Empty
How to fix BeautifulSoup errors — bs4.FeatureNotFound install lxml, find_all returns empty list, Unicode decode error, JavaScript-rendered content not found, select vs find_all confusion, and slow parsing on large HTML.
Fix: Dash Not Working — Callback Errors, Pattern Matching, and State Management
How to fix Dash errors — circular dependency in callbacks, pattern matching callback not firing, missing attribute clientside_callback, DataTable filtering not working, clientside JavaScript errors, Input Output State confusion, and async callback delays.
Fix: dbt Not Working — ref() Not Found, Schema Mismatch, and Compilation Errors
How to fix dbt errors — ref() model not found, profile not found, database relation does not exist, incremental model schema mismatch requiring full-refresh, dbt deps failure, Jinja compilation errors, and test failures.