How to Add Ruff to Pre-Commit Hooks for Python Projects
I kept forgetting to run the linter before committing my Python code. I would push my changes, wait for CI to run, and then get a notification that my build failed because of a formatting issue or an unused import. It was embarrassing and a waste of time. I needed a way to force myself to run the linter before every commit. That is when I discovered pre-commit hooks.
The Problem
When you are deep in a coding session, the last thing on your mind is running linting tools. You make a change, you commit, you push. Then CI runs and tells you that you forgot to format your code or that you have a lint error. Now you have to context switch back, make the fix, amend your commit, and push again. It breaks your flow and slows down your team.
I wanted something that would automatically check my code every time I ran git commit. If there was an issue, the commit would be blocked until I fixed it. Pre-commit hooks are exactly that.
Setting Up Pre-Commit
First, I needed to install the pre-commit framework. It is a Python package that manages git hooks for you:
$ pip install pre-commitAfter installing the package, I thought I was done. I ran a commit and nothing happened. I forgot the most important step. You have to install the hooks into your git repository:
$ pre-commit installThis command creates the necessary hook files in .git/hooks/. Now every time you run git commit, pre-commit will run all the configured hooks.
Adding Ruff Hooks
Ruff provides official pre-commit hooks at the astral-sh/ruff-pre-commit repository. There are two hooks available:
- ruff-check - Runs the linter to catch errors and code quality issues
- ruff-format - Runs the formatter to enforce consistent style
The order of these hooks matters. You should run ruff-check before ruff-format. The linter might fix some issues that then need to be formatted. If you run them in the wrong order, you might need to run them twice.
Here is the configuration I added to my .pre-commit-config.yaml file:
repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: v0.15.8 hooks: # Run the linter. - id: ruff-check # Run the formatter. - id: ruff-formatThe rev tag pins the Ruff version. This is important. Without it, you might get different behavior across different machines or over time. Pinning the version ensures everyone on your team gets the same linting results.
Running Hooks Manually
Sometimes you want to run the hooks on all files, not just the ones you changed. Maybe you inherited a codebase or you are setting this up for the first time. Pre-commit has a command for that:
$ pre-commit run --all-filesThis runs all hooks against every file in your repository. It is useful for catching existing issues before they become your problem.
Auto-Fixing Issues
I got tired of seeing the same lint errors over and over. Missing newlines at the end of files, unused imports, trailing whitespace. Ruff can automatically fix many of these issues. I configured the hook to run with auto-fix:
repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.15.8 hooks: - id: ruff-check args: [--fix] # auto-fix errors - id: ruff-formatNow when I commit, Ruff automatically fixes what it can. The commit still fails if there are issues it cannot fix automatically, but the simple stuff gets handled without my intervention.
How This Changed My Workflow
Since setting up pre-commit hooks with Ruff, my CI builds pass on the first try much more often. I catch errors at commit time, not after waiting for CI. The feedback loop is immediate. If my code has an issue, I know within seconds, not minutes.
My team also benefits. Everyone has the same hooks configured. We enforce consistent code style without having to remind each other in code reviews. The hooks are the gatekeeper.
Common Mistakes to Avoid
I made a few mistakes when I first set this up. Here is what I learned:
Not pinning the Ruff version. The rev tag is not optional. Without it, you can get different behavior over time as Ruff releases new versions. Pin it.
Forgetting pre-commit install. I installed the package and thought I was done. The hooks do not actually run until you install them into your local git repository.
Running hooks in the wrong order. ruff-check should come before ruff-format. The linter might make changes that then need formatting.
Not committing the config file. The .pre-commit-config.yaml file should be checked into your repository. Every team member needs the same configuration.
Related Knowledge
Pre-commit hooks integrate directly with git. When you run git commit, git looks for an executable file at .git/hooks/pre-commit. The pre-commit framework manages this file for you and runs your configured hooks in sequence.
You can also run pre-commit in CI. This ensures that even if someone bypasses the hooks locally, the code still gets checked. Many CI providers have first-class support for pre-commit, or you can just run pre-commit run --all-files as a CI step.
For VS Code users, there is also the Ruff extension that runs linting and formatting as you type. The pre-commit hooks provide a safety net for when you forget to save with formatting enabled or when using other editors.
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