Skip to content

Fix: Ruff Not Working — Configuration Errors, Rule Selection, and Format vs Lint Confusion

FixDevs ·

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 ignore

Or 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 F841

Or 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 line

Ruff 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 behavior

ruff 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 change

Run both in CI:

ruff check .                  # Fail on lint issues
ruff format --check .         # Fail if files aren't formatted

Common workflow — format then lint:

ruff format .                 # Apply formatting changes
ruff check --fix .            # Auto-fix lint issues
ruff check .                  # Verify nothing remains

Pro 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:

  1. pyproject.toml in the file’s directory
  2. pyproject.toml walking up the directory tree
  3. ruff.toml (no [tool.ruff] prefix needed)
  4. .ruff.toml (same as ruff.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 from

Fix 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:

PrefixSourceExamples
E, WpycodestyleE501 (line too long), W292 (no newline at EOF)
FpyflakesF401 (unused import), F841 (unused variable)
IisortI001 (import block sorting)
UPpyupgradeUP007 (use X | Y instead of Optional[X])
Bflake8-bugbearB008 (mutable default argument)
Npep8-namingN802 (function name should be lowercase)
DpydocstyleD100 (missing docstring)
Sflake8-banditS101 (use of assert)
RUFRuff-specificRUF001 (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 rulesruff 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 triggered

Select everything except specific rules:

[tool.ruff.lint]
select = ["ALL"]              # Enable every rule
ignore = ["D", "ANN", "FA"]   # Disable docstring, annotation, future-annotations rules

ALL 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 F841

Cause 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  # noqa

Cause 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_module

Disable 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 file

Fix 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 statements

Pre-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-format

Fix 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,W503

New 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 quotes

Run 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 lengths

Solution 2: Use the same line length for both:

[tool.ruff]
line-length = 100   # Applies to both formatter and linter

Solution 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: E501

The 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 behavior

Safe 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 behavior
  • F841: 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 --fix

This 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-format

Editor 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:

  1. Excessive select = ["ALL"] — enables hundreds of rules including slow ones (D, ANN)
  2. Large exclude lists with complex patterns
  3. Running Ruff via Python — the ruff CLI is a Rust binary; running through python -m ruff adds Python startup overhead
# Measure
time ruff check .
# Should be under 1s for thousands of files

Installation 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 ruff

Both 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles