Skip to content

How to Use Ruff CLI: Essential Flags for Linting and Formatting

I installed ruff and ran ruff check . on my Python project. The output showed 47 violations across 12 files. But how do I fix them? What if I want to preview changes before applying them? How do I override my config for a specific run?

I spent the next hour reading ruff’s CLI documentation. Turns out ruff has a powerful command-line interface with flags that make my workflow significantly smoother. Let me show you the essential commands and flags I use daily.

The Two Main Commands: check and format

Ruff’s CLI has two primary commands:

Ruff's main commands
ruff check → Linting (find code issues, violations)
ruff format → Formatting (apply consistent code style)

Everything else builds on these two. I used to think ruff was just a linter, but the ruff format command replaces black entirely. Here’s my typical workflow:

Daily lint and format workflow
# First, check what issues exist
ruff check src/
# Fix the issues ruff can auto-fix
ruff check --fix src/
# Then format the code
ruff format src/

I run these before every commit. The entire process takes about 0.5 seconds on my project with 200 files.

The —fix Flag: Your Best Friend

The --fix flag is the most useful CLI option. It automatically fixes violations that ruff can safely correct:

Auto-fix lint violations
ruff check --fix src/

What gets fixed automatically? Import sorting, unused imports, trailing whitespace, missing newlines at file end, and many more. Ruff can fix about 200 different rule violations automatically.

I made a mistake early on: I ran --fix without checking what would change first. This modified files I didn’t expect. The better approach:

Safe fix workflow
# First, see what will be fixed
ruff check --diff src/
# Then apply the fixes
ruff check --fix src/

The --diff flag shows what changes will be made without actually modifying files. I always run --diff before --fix on unfamiliar codebases.

The —diff Flag: Preview Before Commit

The --diff flag shows a git-style diff of what ruff would change:

Preview lint fixes without applying
ruff check --diff src/

Output looks like this:

Example --diff output
--- src/utils.py
+++ src/utils.py
@@ -1,4 +1,5 @@
import os
+import sys # Added missing import
from pathlib import Path
def process_file(path):
- data = open(path).read()
+ with open(path) as f: # Safe file handling
+ data = f.read()
return data

I use --diff in three scenarios:

  1. Before fixing unfamiliar code - See what changes will be made
  2. In code reviews - Show reviewers what ruff would change
  3. Debugging config issues - Verify my configuration applies correctly

The —unsafe-fixes Flag: When You Need More Fixes

Some fixes are marked as “unsafe” because they might change code behavior. Ruff won’t apply these by default:

Include unsafe fixes
ruff check --fix --unsafe-fixes src/

What makes a fix “unsafe”? Examples include:

  • Removing unused variables (might be intentional for debugging)
  • Changing == None to is None (affects some custom __eq__ implementations)
  • Rewriting certain comprehensions

I avoid --unsafe-fixes in CI pipelines. But during local development, I sometimes use it to clean up code quickly, then review the changes carefully.

The —check Flag for Format: CI Integration

The ruff format command modifies files by default. For CI, I use --check to fail if formatting is needed:

Check format without modifying (CI use)
ruff format --check src/

This exits with code 1 if any files need formatting, failing the CI build. Combined with lint check:

Full CI lint check
ruff check src/
ruff format --check src/

Both commands must pass. No files are modified.

The —config Flag: Override Settings from CLI

The --config flag does two things: specify a config file path, or override specific settings:

Specify config file path
ruff check --config custom-ruff.toml src/

More useful for me: overriding settings without modifying my config file:

Override config settings via CLI
ruff check --config "lint.select=['E','F','B']" src/
ruff check --config "line-length=100" src/
ruff check --config "lint.ignore=['E501','F401']" src/

Each --config flag accepts a TOML key=value pair. I use this when:

  • Running a stricter check on critical modules
  • Temporarily relaxing rules during debugging
  • Testing different configurations before committing

Multiple overrides work by passing multiple --config flags:

Multiple config overrides
ruff check \
--config "lint.select=['E','F','B','S']" \
--config "line-length=120" \
src/

The —select and —ignore Flags: Quick Rule Control

Instead of editing pyproject.toml, I can control rules directly from CLI:

Select specific rules from CLI
ruff check --select E,F,B src/
ruff check --select ALL src/

The --ignore flag excludes rules:

Ignore specific rules from CLI
ruff check --ignore E501,F401 src/

These flags override any config file settings. I use them for:

  • Running security-only checks: ruff check --select S src/
  • Testing a stricter configuration: ruff check --select ALL --ignore D src/
  • One-off checks without modifying config

The —output-format Flag: Machine-Readable Output

For CI integration or scripts, I use structured output formats:

JSON output for CI integration
ruff check --output-format=json src/

Available formats:

Output format options
json → JSON array of violations
junit → JUnit XML (for test reporting tools)
sarif → SARIF format (for GitHub code scanning)
github → GitHub Actions annotations
gitlab → GitLab Code Quality report
concise → Minimal human-readable output
full → Detailed human-readable output (default)

For GitHub Actions, the github format creates annotations directly in the PR:

.github/workflows/lint.yml
name: Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Lint with ruff
run: uvx ruff check --output-format=github src/
- name: Check format
run: uvx ruff format --check src/

The —watch Flag: Continuous Development

The --watch flag runs ruff continuously, re-checking on file changes:

Watch mode for development
ruff check --watch src/

I use this during active development. As I edit files, ruff immediately shows new violations. The feedback loop is instant.

Combine with --fix for live fixing:

Watch and auto-fix
ruff check --watch --fix src/

Warning: This modifies files as you work. I prefer watching without fixing, then running --fix manually when ready.

The —exit-non-zero-on-fix Flag: Catch CI Modifications

In CI, if ruff fixes files, I want the build to fail so I know there were issues:

Fail CI if fixes were applied
ruff check --fix --exit-non-zero-on-fix src/

This applies fixes but exits with code 1 if any fixes were made. In CI, this means:

  • Files get fixed (committed back via CI automation)
  • Build fails, notifying me that code needed cleanup
  • I can review the fixes before merging

Without this flag, --fix exits with code 0 even if files were modified, which can silently change code in CI without notification.

Other Useful Commands

Ruff has several utility commands:

Show help for a specific rule
ruff rule E501
ruff rule F401
ruff rule S101

This explains what a rule does and why it matters. I use this when I encounter unfamiliar error codes.

Show current configuration
ruff config
ruff config lint.select
ruff config line-length

This shows your effective configuration after loading from all sources.

Clear ruff cache
ruff clean

Ruff caches parsed files for speed. ruff clean removes the cache. I rarely need this, but it helps when debugging strange behavior.

Common Mistakes I Made

Mistake 1: Using —fix Without —diff First

I ran ruff check --fix src/ on a shared repository. It modified files other developers were working on. Now I always run --diff first to preview changes.

Mistake 2: Not Understanding CLI Overrides Config

I spent 30 minutes debugging why my pyproject.toml config wasn’t working. Turns out I had a --select E,F flag in my shell alias that was overriding everything. CLI flags always override config files.

Mistake 3: Missing —exit-non-zero-on-fix in CI

My CI pipeline ran ruff check --fix but always passed, even when it modified files. I didn’t realize fixes were happening. Adding --exit-non-zero-on-fix made CI notify me of changes.

Mistake 4: Using —unsafe-fixes Carelessly

I ran --unsafe-fixes on a project and it removed variables I was using for debugging. The code still ran, but my debug output disappeared. Now I only use unsafe fixes after careful review.

Summary

In this post, I showed you the essential Ruff CLI commands and flags I use daily. The key points are:

  • ruff check for linting, ruff format for formatting
  • --fix applies automatic fixes, but use --diff first to preview
  • --unsafe-fixes includes fixes that might change code behavior
  • --config accepts file paths or TOML key=value overrides
  • --select and --ignore control rules directly from CLI
  • --output-format=json enables CI integration
  • --watch runs continuously during development
  • --exit-non-zero-on-fix catches modifications in CI

Start with the basics: ruff check . to find issues, ruff check --diff . to preview fixes, ruff check --fix . to apply fixes, and ruff format . to format. Add flags as your workflow needs them.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments