Astral shipped Ruff v0.16.0 on July 23rd, and the update quietly rewires how the popular Python linter behaves out of the box. According to Simon Willison, who flagged the release, the change is big enough that his CI jobs started failing overnight thanks to new default checks and an unpinned “ruff” dependency. If you run Ruff anywhere in your stack, this one lands on your desk whether you asked for it or not.
Here’s the headline number, straight from Brent Westbrook’s announcement: Ruff now enables 413 rules by default, up from just 59 in previous versions. That’s not a small bump. It’s a philosophical shift in what Ruff assumes you want checked without any configuration.
What changed
Ruff’s default rule set had barely moved since v0.1.0. In the meantime, the total number of available rules grew from 708 to 968. Many of the strongest checks, including ones that catch syntax errors and immediate runtime errors, were sitting there switched off by default. Version 0.16.0 flips a big chunk of them on.
Key points from the release:
- 413 rules on by default, up from 59.
- 968 total rules available, up from 708 at v0.1.0.
- Newly enabled checks catch severe issues, including syntax and runtime errors that used to slip past a default run.
- No config needed. Ruff surfaces these problems automatically.
Try it in one line
Simon Willison shares a quick way to test it on any Python project:
uvx ruff@latest check .
Want Ruff to fix what it can? He used this to do the bulk of the work:
uvx ruff@latest check . --fix --unsafe-fixes
He ran the latest Ruff against his three biggest projects: Datasette, sqlite-utils, and LLM. It found hundreds of minor issues that breached the new defaults. On sqlite-utils alone, the fix command reported 1,618 errors, with 1,538 fixed automatically and 80 remaining.
What the errors look like
One thing worth calling out: Ruff explains each problem clearly, with the offending line and a suggested fix. Willison shares a few examples from the leftovers:
- DTZ005:
datetime.datetime.now()called without atzargument. Ruff suggests passing adatetime.timezoneobject. - BLE001: catching a blind
Exceptioninstead of something specific. - B018: a useless attribute access that should be assigned or removed.
These aren’t obscure nitpicks. They’re the kind of quiet bugs that survive for years until something breaks in production.
Why the upgrade felt safe
Willison notes his projects all ship with comprehensive test suites, run in CI against Python 3.10 through 3.14. That safety net is what made a mass auto-fix reasonable. Change hundreds of lines, run the tests, trust the green check. Without solid coverage, running --unsafe-fixes across a large codebase is a lot riskier, so treat that command with respect.
The agent angle
There’s a strategic detail here. Astral now sits inside OpenAI, and Ruff’s output reads like it was built for the moment. Each error comes with the file, the line, the rule code, and a plain-English fix. That’s exactly what a coding agent needs to clean up a project without hand-holding.
Willison leaned into it: he had Codex upgrade LLM and sqlite-utils, and Claude Code handle Datasette. The linter finds the issues, the agent applies the fixes, the tests confirm nothing broke. It’s a preview of how a lot of maintenance work is going to get done.
Why it matters
If your CI pins Ruff, nothing breaks until you choose to move. If it doesn’t, expect a wall of new warnings on your next run. Either way, the smart play is to pin the version, then upgrade deliberately behind a strong test suite. The payoff is real: hundreds of latent bugs surfaced with a single command, and a fix loop that a coding agent can largely drive on its own.
Full details, including Brent Westbrook’s original announcement, are covered in Simon Willison’s writeup.