Here's a number that changed how we think about code quality: fixing a bug at commit time takes 2 minutes. Fixing the same bug in production takes 40+ hours. That's a 1,200x cost difference.
The question isn't whether to automate quality checks. It's where and how fast.
Three layers, strict speed budgets
Speed budgets aren't optional. They're the difference between a quality system that sticks and one that gets bypassed within a week.
Layer 1: Pre-commit (budget: under 5 seconds)
Runs on every save. If it's slow, developers will add --no-verify to muscle memory.
# .pre-commit-config.yaml
hooks:
- ruff --fix # Auto-fix formatting + imports
- ruff-format # Consistent code style
- check-yaml # Valid YAML
- detect-private-key # Catch committed secrets Note: auto-fix, not just check. Developers shouldn't have to think about formatting. The tool handles it.
Layer 2: Pre-push (budget: under 60 seconds)
Runs before code leaves your machine. Catches the stuff that takes longer to check.
# .git/hooks/pre-push
mypy app/ # Full type check
pytest tests/unit/ -x # Unit tests (fail-fast)
ruff check --select S # Security-focused rules Layer 3: CI/CD (budget: under 5 minutes)
The final gate before merge. Comprehensive, thorough, no shortcuts.
# .github/workflows/quality-gates.yml
- ruff format --check . # Formatting
- ruff check . # Full lint suite
- mypy app/ --strict # Strict type checking
- pytest --cov-fail-under=80 -v # Tests + coverage floor
- pip-audit --strict # Dependency vulnerabilities The results
| Layer | Issues caught | Avg fix time | Cost multiplier |
|---|---|---|---|
| Pre-commit | 1,200+ | 2 min | 1x |
| Pre-push | 895 | 15 min | 8x |
| CI/CD | 377 | 45 min | 23x |
| Human review | 378 | 3 hours | 90x |
87% of all issues caught before any human looked at the code. That's not a testing stat — it's a team velocity multiplier. Your reviewers spend their time on architecture and logic instead of pointing out formatting and type errors.
Roll it out gradually
Don't add everything at once. Developers revolt against sudden, sweeping tooling changes.
| When | Add |
|---|---|
| Week 1 | Formatting only (zero friction, instant consistency) |
| Week 2 | Basic linting rules |
| Month 1 | Type checking (permissive), coverage floor at 60% |
| Month 2 | Security scanning, coverage up to 70% |
| Month 3 | Full strict suite, coverage at 80% |
Why speed budgets are non-negotiable
The fastest way to destroy a quality system is making it slow. Here's what happens:
- Pre-commit hook takes 30 seconds
- Developers start using
--no-verify - Issues pile up in CI instead of getting caught locally
- CI takes 15 minutes because it's catching everything
- Developers push and go get coffee instead of fixing issues immediately
- Context switch. Issues are now 10x harder to fix.
Keep pre-commit under 5 seconds. This is the one thing you don't compromise on.