Pre-Commit Hooks & Ruff Linter: Automated Code Quality
Code quality issues discovered during manual review waste time and delay deployments. A single missing newline, trailing whitespace, or inconsistent formatting triggers review comments that could have been caught automatically. We implemented pre-commit hooks with Ruff linter to shift quality checks left, catching issues at commit time rather than review time.
The Problem: Manual Code Quality Enforcement
Before implementing automated checks, every pull request went through manual review where reviewers spent time identifying formatting issues, linting errors, and style inconsistencies. A developer would commit code, push it, create a PR, wait for review, receive formatting feedback, fix the issues locally, push again, and wait for re-review. This cycle repeated for every minor style violation.
The feedback loop was slow. From commit to feedback took anywhere from 30 minutes to several hours, depending on reviewer availability. Developers context-switched between tasks while waiting, reducing productivity. Reviewers spent mental energy on trivial issues instead of focusing on logic and architecture.
Before: Manual Review Workflow
Development Workflow
┌──────────────────┐ ┌──────────────────┐
│ Write Code │──────>│ Manual Review │
│ (Any format) │ │ - Style issues │
│ │ │ - Linting errors │
│ │ │ - Long feedback │
└──────────────────┘ └──────────────────┘
~2 hours ~30-60 min
Common review comments included: "Remove trailing whitespace," "Add newline at end of file," "Fix import order," "Line too long," "Missing docstring." These comments appeared in 80% of pull requests, creating noise that obscured substantive feedback.
The Solution: Pre-Commit Hooks with Ruff
We configured pre-commit hooks to run automatically before each git commit. When a developer attempts to commit code, pre-commit runs a series of checks. If any check fails, the commit is blocked, and the developer sees exactly what needs fixing. Most checks auto-fix the issues, requiring only a re-commit with corrected files.
After: Automated Pre-Commit Workflow
Development Workflow
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Write Code │──────>│ Pre-Commit │──────>│ Fast Review │
│ │ │ - Auto format │ │ - Focus on logic │
│ │ │ - Lint check │ │ - Less churn │
│ │ │ - Instant FB │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘
~2 hours <5 seconds ~10-15 min
The feedback loop collapsed from hours to seconds. Developers see and fix issues instantly, before code leaves their machine. Reviews focus on business logic, architecture, and edge cases—the areas where human judgment adds value.
Implementation Details
We created .pre-commit-config.yaml in the repository root, defining five hooks:
Pre-Commit Configuration:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.9
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
Each hook serves a specific purpose:
- trailing-whitespace - Removes trailing spaces at line ends
- end-of-file-fixer - Ensures files end with a single newline
- check-yaml - Validates YAML syntax in configuration files
- ruff - Python linter that checks for code smells and anti-patterns
- ruff-format - Auto-formats Python code to consistent style
- detect-secrets - Prevents accidental credential commits
Ruff replaced multiple slower tools (Flake8, Black, isort) with a single Rust-based linter that runs 10-100× faster. Configuration lives in pyproject.toml:
Ruff Configuration:
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
]
ignore = [
"E501", # line too long (handled by formatter)
]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
Installation required two steps:
# Install pre-commit framework
pip install pre-commit
# Install git hooks
pre-commit install
After installation, every git commit triggers the hooks automatically. Developers can also run checks manually:
# Check all files
pre-commit run --all-files
# Check specific file
pre-commit run --files src/services/drip.py
Integration with CI/CD
We added pre-commit to our CI pipeline to catch cases where developers bypass local hooks:
CircleCI Configuration:
jobs:
lint:
docker:
- image: python:3.11
steps:
- checkout
- run: pip install pre-commit
- run: pre-commit run --all-files
If a developer commits without running hooks (via git commit --no-verify or not installing hooks), the CI build fails. This creates a safety net while encouraging proper local setup.
Real-World Impact
Within the first week, we saw measurable improvements:
Pull Request Metrics:
- Review comments about formatting: 80% → 0%
- Average time to first approval: 2.5 hours → 45 minutes
- Review cycles per PR: 2.3 → 1.2
- Code style consistency: 60% → 100%
Developer Experience:
- Time spent on formatting: 15 min/day → 0 min/day
- Context switches while waiting for review: 3-4/day → 1-2/day
- Confidence in code quality: Self-reported increase from 6/10 to 9/10
Example Fix Speed:
Manual Process:
Commit → Push → PR → Review → Fix → Push → Re-review
Total: 2-4 hours
Automated Process:
Commit → Pre-commit runs → Auto-fix → Commit
Total: 5 seconds
The most significant benefit wasn't time savings—though 5-10 minutes saved per PR adds up across hundreds of PRs. The real win was mental bandwidth. Reviewers stopped commenting on trivial issues. Developers stopped worrying about whether they remembered to run the formatter. Code style became invisible infrastructure that "just worked."
Challenges and Solutions
Challenge 1: Slow Hook Execution Initial configuration ran all hooks on all files, taking 30-45 seconds per commit. Developers complained about workflow interruption.
Solution: We configured hooks to run only on staged files, reducing execution time to 2-3 seconds for typical commits. Large refactorings still take longer, but normal development flows smoothly.
Challenge 2: Conflicts with Editor Formatting Some developers used VS Code auto-save with format-on-save, which sometimes conflicted with Ruff's formatting.
Solution: We standardized on Ruff formatting across all environments. Developers configured their editors to use Ruff as the formatter, ensuring consistency between editor and pre-commit.
Challenge 3: Learning Curve for New Contributors New team members forgot to install hooks, leading to CI failures.
Solution: We added a check to the CI pipeline that fails fast if pre-commit isn't installed locally, with a clear error message directing to installation instructions in the README.
Results
Before Implementation:
- 100% of code reviews included formatting feedback
- Average PR review time: 2.5 hours
- Code style inconsistencies across the codebase
- Developers spent 15 min/day on manual formatting
After Implementation:
- 0% of code reviews include formatting feedback
- Average PR review time: 45 minutes (64% reduction)
- 100% consistent code style
- Developers spend 0 min/day on manual formatting
- 5-10 minutes saved per pull request
- Faster CI builds (early linting failure detection)
Pre-commit hooks became invisible infrastructure that improved quality without adding cognitive load. Developers commit code, hooks run, issues get fixed, and reviews focus on what matters. The investment—a few hours of configuration—paid off within the first week and continues delivering value on every commit.
Key Takeaways
- Shift quality checks left - Catch issues at commit time, not review time
- Automate the trivial - Free human reviewers to focus on complex logic
- Make it fast - Slow checks get bypassed; Ruff's speed ensures adoption
- Standardize tooling - One formatter, one linter, configured once
- Measure impact - Track review time and feedback patterns to prove value
The code quality improvement wasn't about catching more bugs—it was about reducing friction in the development workflow. By automating formatting and linting, we removed a category of problems entirely, making code reviews faster and more focused on architectural and logical concerns.