Ruff Setup skill for Codex

One pinned Ruff in place of flake8, Black and isort. Codex configures Ruff in pyproject.toml with a deliberate rule tier rather than the bare defaults or ALL, leaves out rules that conflict with the formatter, and translates flake8 plugins, isort profiles and Black options using a bundled mapping table. Before and after counts per rule code prove nothing was lost, mass fixes land as separate commits, and the same pinned version runs in pre-commit and CI.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- ruff-setup

Installs to ~/.agents/skills/ruff-setup/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $ruff-setup, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Configures Ruff as the Python linter and formatter in pyproject.toml or ruff.toml - rule selection beyond the defaults, per-file-ignores, isort settings, formatter options that avoid lint conflicts, pinned versions - and migrates projects off flake8 and its plugins, Black, isort, pyupgrade and autoflake with a measured baseline and noqa backfill. Use when adding Ruff to a Python project, replacing flake8 or Black with Ruff, choosing which Ruff rules to enable, or fixing Ruff results that differ between local runs, pre-commit and CI.

Use it when

  • Adding linting and formatting to a Python project
  • Migrating from flake8 and its plugins, Black, isort or pyupgrade
  • Deciding which Ruff rule prefixes to enable
  • Ruff results differ between editor, pre-commit and CI

Use something else when

  • You need static type checking with mypy or pyright: use python-typing
  • You are only wiring hooks for already-configured tools: use pre-commit-hooks
  • The project is JavaScript or TypeScript: use biome-setup

How to check the result

ruff check --statistics shows only expected codes with counts explained against the old flake8 baseline, and ruff check plus ruff format --check pass in CI with the same pinned version.

The mistake it prevents

Using exclude instead of extend-exclude, which wipes Ruff's default exclusions and starts linting .venv and build directories.

~/.agents/skills/ruff-setup/SKILL.md

--- name: ruff-setup description: Configures Ruff as the Python linter and formatter in pyproject.toml or ruff.toml - rule selection beyond the defaults, per-file-ignores, isort… license: MIT ---

Done well means one Ruff version pinned across local, pre-commit and CI; a rule selection chosen on purpose (not ALL, not just the defaults); no rules that fight the formatter; and ruff check plus ruff format --check both clean in CI, with any legacy violations either fixed or recorded as explicit noqa comments.

Inspect

ls pyproject.toml ruff.toml .ruff.toml setup.cfg .flake8 tox.ini .isort.cfg .pre-commit-config.yaml 2>/dev/null
grep -nA30 '^\[tool\.\(ruff\|black\|isort\|flake8\)' pyproject.toml
grep -nE 'ruff|flake8|black|isort|pyupgrade|autoflake|pylint' requirements*.txt pyproject.toml .pre-commit-config.yaml 2>/dev/null

Read AGENTS.md for the dependency manager (uv, poetry, pip-tools) and check commands. Note project.requires-python; Ruff infers target-version from it.

Install

uv add --dev ruff==0.17.3        # or: poetry add --group dev [email protected] ; pip install ruff==0.17.3

Use the current release at install time and pin it exactly: new Ruff versions add rules and promote preview behaviour, which can turn CI red on unrelated PRs. Installing needs network; in an offline Codex sandbox ask for approval or use a Ruff already in the venv.

Reference configuration

# pyproject.toml
[tool.ruff]
line-length = 100
extend-exclude = ["migrations/versions", "src/generated"]
# target-version comes from project.requires-python; set it only if that is missing

[tool.ruff.lint]
select = [
  "E4", "E7", "E9", "F",   # Ruff defaults: real errors only
  "I",                     # isort
  "B",                     # bugbear: likely bugs
  "UP",                    # pyupgrade for the target version
  "SIM",                   # simplifications
  "C4",                    # comprehensions
  "PT",                    # pytest style
  "RUF",                   # Ruff-specific, includes unused-noqa
]
ignore = ["SIM108"]        # ternary rewrite: taste, often less readable

[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101", "PLR2004"]
"__init__.py" = ["F401"]
"scripts/**" = ["T20"]

[tool.ruff.lint.isort]
known-first-party = ["myapp"]

[tool.ruff.format]
docstring-code-format = true

Choosing rules

TierPrefixesUse when
BaselineE4 E7 E9 F (default)Never less than this
Recommended for most code+ I B UP SIM C4 RUFDefault choice for applications and libraries
Domain add-onsPT (pytest), DJ (Django), FAST (FastAPI), ASYNC (asyncio), S (bandit security), DTZ (timezones), PD/NPY (data)The project uses that domain
Library polishD with a convention, N, PL*, RET, PTH, TCPublic libraries with a mature codebase
AvoidALLEvery upgrade silently enables new rules; also selects conflicting pairs
Leave off with ruff formatW191 E111 E114 E117 D206 D300 Q000-Q003 COM812 COM819, and ISC001/ISC002 if Ruff warnsThey conflict with the formatter; Ruff prints a warning when they are on

Explain each non-default prefix in a comment, as above. For any single code: ruff rule B008 prints its rationale and fix behaviour offline.

Migrating from flake8, Black and isort

For the plugin-to-prefix table and settings translation (max-complexity, isort profile, Black string normalisation), read references/flake8-plugin-map.md when the project has an existing flake8, isort, Black or pylint configuration.

- [ ] 1. Record baseline: current tool versions, violation counts per code
- [ ] 2. Translate settings and plugins to [tool.ruff] (reference file)
- [ ] 3. ruff check --statistics: compare counts with the old flake8 run
- [ ] 4. Formatter: ruff format --diff; review differences from Black
- [ ] 5. Commit config; commit safe autofixes; commit the reformat separately
- [ ] 6. Backfill noqa for what remains (or fix it) and enable RUF100
- [ ] 7. Replace old tools in pre-commit, CI, dev deps and editor settings
- [ ] 8. Add mass-change SHAs to .git-blame-ignore-revs

Commands for each step:

flake8 --statistics -qq . | sort -rn | head -30         # before (old tool still installed)
ruff check --statistics .                               # after: compare codes and counts
ruff check --fix .                                      # safe fixes only
ruff check --fix --unsafe-fixes --diff .                # preview unsafe fixes; apply only after review
ruff format --diff . | head -100                        # Black -> Ruff formatter differences
ruff check --add-noqa .                                 # baseline remaining violations as noqa comments
ruff check --extend-select RUF100 --fix .               # later: remove noqa that no longer apply
  • Ruff’s formatter is designed as a drop-in for Black and differs on a small number of edge cases. Expect a handful of changed files, not hundreds; hundreds means a setting (line length, quote style, magic trailing comma) was not carried over.
  • ruff format does not sort imports. Keep I selected and run ruff check --fix before ruff format.
  • --add-noqa is a debt ledger, not a fix. Prefer fixing when counts are small; use noqa backfill when a large codebase adopts new rules, and tell the user how many lines it touched.

Running everywhere the same way

ruff check . && ruff format --check .                  # CI gate
ruff check --output-format=github .                    # GitHub Actions annotations
ruff check --show-settings src/myapp/api.py | head -50 # resolved settings for one file
ruff check --show-files . | head                       # which files Ruff will lint

pre-commit (lint with --fix before format):

- repo: https://github.com/astral-sh/ruff-pre-commit
  rev: v0.17.3              # same version as the dev dependency
  hooks:
    - id: ruff-check
      args: [--fix]
    - id: ruff-format

Editor: the Ruff VS Code extension uses the project’s ruff from the environment when it can; set it as the Python formatter and disable Black, isort and flake8 extensions so they do not compete.

Configuration discovery gotchas

  • Ruff uses the closest pyproject.toml (with a [tool.ruff] section), ruff.toml or .ruff.toml to each file. A nested config replaces the parent entirely unless it says extend = "../pyproject.toml". In monorepos, give packages extend or keep one root config.
  • exclude replaces Ruff’s default exclusions (.venv, build, node_modules, and so on); use extend-exclude instead.
  • Files passed explicitly on the command line are linted even if excluded, unless force-exclude = true. Set it when pre-commit or editors pass paths directly.
  • per-file-ignores keys are globs relative to the config file’s directory.
  • select in a nested config overrides, extend-select adds. Mixing them up silently drops rules.

Verify

  1. ruff check --statistics . shows only the codes you expect, with counts you can explain against the baseline.
  2. ruff check . && ruff format --check . exit 0 locally and in CI.
  3. pre-commit run --all-files produces no changes on a clean tree.
  4. The Ruff version is identical in the lockfile, pre-commit rev and CI.

Report: Ruff version, selected prefixes with one-line reasons, before/after violation counts, files reformatted, number of noqa comments added, old tools removed.

What the zip contains

Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Ruff Setup"
  short_description: "One pinned Ruff in place of flake8, Black and isort"
  default_prompt: "$ruff-setup Replace flake8, isort and Black with Ruff in this project without losing any checks"
references/flake8-plugin-map.md98 lines, 5.0 KB
# Mapping existing Python tooling to Ruff

Read this while migrating, to translate an existing flake8 / isort / Black / pylint setup
into `select` codes and settings. Confirm any code with `ruff rule <CODE>` or
`ruff linter`, since prefixes occasionally change between Ruff releases (for example
flake8-type-checking moved from `TCH` to `TC`).

## Tools and flake8 plugins to rule prefixes

| Existing tool or plugin | Ruff prefix | Notes |
|---|---|---|
| pycodestyle | `E`, `W` | Ruff's default selects only `E4`, `E7`, `E9`; whitespace rules overlap with the formatter |
| pyflakes | `F` | Default-on |
| autoflake | `F401`, `F841` with `--fix` | Unused imports and variables |
| mccabe | `C90` | Threshold via `lint.mccabe.max-complexity` |
| isort | `I` | Run with `ruff check --select I --fix`; `ruff format` does not sort imports |
| pyupgrade | `UP` | Respects `target-version` / `requires-python` |
| flake8-bugbear | `B` | High value, low noise; add to every project |
| flake8-comprehensions | `C4` | |
| flake8-simplify | `SIM` | Some suggestions are taste; ignore individual codes freely |
| flake8-bandit | `S` | Ignore `S101` (assert) under tests |
| flake8-builtins | `A` | |
| flake8-print | `T20` | Good for libraries and services; skip for CLIs that print by design |
| flake8-debugger | `T10` | |
| flake8-pytest-style | `PT` | |
| flake8-quotes | `Q` | Conflicts with the formatter; leave off when using `ruff format` |
| flake8-commas | `COM` | `COM812` conflicts with the formatter; leave off |
| flake8-implicit-str-concat | `ISC` | `ISC001` may conflict with the formatter; Ruff warns if so |
| flake8-annotations | `ANN` | Usually redundant when mypy or pyright enforce annotations |
| flake8-type-checking | `TC` | Formerly `TCH` |
| flake8-tidy-imports | `TID` | Banned imports via `lint.flake8-tidy-imports.banned-api` |
| flake8-import-conventions | `ICN` | `import numpy as np` style aliases |
| flake8-use-pathlib | `PTH` | |
| flake8-return | `RET` | |
| flake8-unused-arguments | `ARG` | |
| flake8-datetimez | `DTZ` | Naive datetime detection |
| flake8-errmsg | `EM` | |
| flake8-logging-format | `G` | |
| flake8-logging | `LOG` | |
| flake8-pie | `PIE` | |
| flake8-raise | `RSE` | |
| flake8-self | `SLF` | |
| flake8-boolean-trap | `FBT` | Noisy on existing code |
| flake8-async | `ASYNC` | |
| flake8-blind-except | `BLE` | |
| flake8-no-pep420 | `INP` | Missing `__init__.py` |
| flake8-2020 | `YTT` | |
| flake8-future-annotations | `FA` | |
| flake8-django | `DJ` | |
| flake8-slots | `SLOT` | |
| flake8-gettext | `INT` | |
| pep8-naming | `N` | |
| pydocstyle | `D` | Pick a convention: `lint.pydocstyle.convention = "google"` (or `numpy`, `pep257`) |
| pydoclint / darglint | `DOC` | Partial coverage; check `ruff linter` for current status |
| eradicate | `ERA` | Commented-out code |
| pandas-vet | `PD` | |
| NumPy-specific | `NPY` | |
| pylint | `PLC`, `PLE`, `PLR`, `PLW` | Subset of pylint; no cross-module inference |
| tryceratops | `TRY` | |
| flynt | `FLY` | |
| perflint | `PERF` | |
| refurb | `FURB` | |
| FastAPI | `FAST` | |
| Airflow | `AIR` | |
| Ruff-specific | `RUF` | Includes `RUF100` (unused `noqa`) |

Plugins with no Ruff equivalent (check before promising parity): wemake-python-styleguide,
flake8-spellcheck, flake8-cognitive-complexity, custom in-house flake8 plugins. Either
keep flake8 running only those plugins (`flake8 --select=<their codes>`) or drop them with
the user's agreement.

## Settings translation

| Old setting | Ruff setting |
|---|---|
| flake8 `max-line-length` / Black `line-length` | `line-length` (top level `[tool.ruff]`) |
| flake8 `max-complexity` | `[tool.ruff.lint.mccabe] max-complexity` |
| flake8 `extend-ignore`, `ignore` | `[tool.ruff.lint] ignore` |
| flake8 `select`, `extend-select` | `[tool.ruff.lint] select`, `extend-select` |
| flake8 `per-file-ignores` | `[tool.ruff.lint.per-file-ignores]` with glob keys |
| flake8 `exclude` | `extend-exclude` (keeps Ruff's defaults such as `.venv`) |
| isort `profile = "black"` | Default behaviour is Black-compatible; nothing to set |
| isort `known_first_party` | `[tool.ruff.lint.isort] known-first-party` |
| isort `force_single_line` | `[tool.ruff.lint.isort] force-single-line = true` |
| isort `combine_as_imports` | `[tool.ruff.lint.isort] combine-as-imports = true` |
| Black `skip-string-normalization` | `[tool.ruff.format] quote-style = "preserve"` |
| Black `skip-magic-trailing-comma` | `[tool.ruff.format] skip-magic-trailing-comma = true` |
| Black `target-version` | `target-version`, or let Ruff read `project.requires-python` |
| pylint `max-args` | `[tool.ruff.lint.pylint] max-args` |
| pylint `disable=` comments | `# noqa: PLR0913` style comments (pylint comments are ignored) |

## Inline suppressions

- flake8 `# noqa: E501` comments work unchanged in Ruff.
- File-level: `# ruff: noqa: F401` (a bare `# flake8: noqa` is also honoured).
- `# type: ignore` and `# pylint: disable` are not Ruff suppressions.
- After migration, enable `RUF100` once to delete `noqa` comments that no longer
  suppress anything: `ruff check --extend-select RUF100 --fix`.

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem