The Hidden Feature Most Claude Code Users Miss
I watched a developer spend 20 minutes yesterday manually running black and pytest after each Claude Code edit. They’d ask Claude to write a function, wait for it to finish, then run their quality checks, find issues, ask Claude to fix them, and repeat the cycle.
There’s a better way.
PostToolUse hooks fire every time Claude touches a Python file. You configure them once in your settings, and from that point forward, every single edit gets automatically formatted, linted, and tested. No manual steps. No forgetting to run the linter. No discovering style violations in code review.
Black Formatter: The One Hook Everyone Needs
Start here. This single hook prevents 90% of formatting debates:
| |
Every Python file Claude edits instantly conforms to PEP 8 standards. No more inconsistent indentation. No more weird line lengths. Black makes the formatting decisions so you don’t have to.
The run_in_background = false setting is intentional. You want formatting to complete before Claude continues, so subsequent edits build on properly formatted code.
The Modern Stack: Ruff + Black
If you’re still running flake8 or pylint, you’re waiting too long. Ruff does the same job in milliseconds:
| |
Ruff’s --fix flag automatically corrects many issues—unused imports, incorrect comparisons, and deprecated patterns. Then Black handles formatting. By the time Claude reports the edit is complete, your code is already clean.
Background Testing That Doesn’t Block
Here’s where it gets interesting. You want tests running constantly, but you don’t want to wait for them:
| |
Notice run_in_background = true. Claude continues working while tests run in parallel. You catch regressions immediately without slowing down the AI.
The file_paths = ["src/**/*.py"] pattern limits this to source files only. Editing test files doesn’t trigger test runs—that would create infinite loops.
Type Checking With Mypy
For typed Python codebases, add type validation:
| |
This catches the “I changed this function’s signature but forgot to update the callers” errors that AI coding assistants love to create.
Security Scanning With Bandit
AI-generated code has a habit of introducing security vulnerabilities. Catch them immediately:
| |
Bandit flags hardcoded credentials, unsafe SQL string building, insecure deserialization, and dozens of other security anti-patterns. When Claude writes eval(user_input) (it happens), you’ll know instantly.
The Complete Quality Pipeline
Set Up Python Quality Hooks
Configure Claude Code to automatically format, lint, type-check, security-scan, and test Python files on every edit
Create or Edit Your Settings File
Claude Code hooks go in your settings file. For global hooks, edit ~/.claude/settings.toml. For project-specific hooks, create a .claude/settings.toml in your project root:
| |
Add the Formatting and Linting Hook
Start with the essentials that should run synchronously:
| |
This handles formatting and linting on every edit.
Add Background Testing
Add a separate hook for tests that runs in parallel:
| |
The --tb=short flag keeps test failure output concise.
Add Type Checking (Optional)
For typed codebases, add mypy validation:
| |
The --ignore-missing-imports flag prevents noise from third-party packages without type stubs.
Specialized Hooks For Specific Workflows
Django Projects
Django has its own validation layer. Add this for model and migration files:
| |
This catches migration issues and model validation errors before they reach your database.
Import Organization
If you want imports sorted consistently (stdlib, then third-party, then local):
| |
The --profile black ensures isort’s output is compatible with Black’s formatting.
Dead Code Detection
Vulture finds unused functions and variables. Run it periodically to catch code rot:
| |
Running in the background means it doesn’t slow you down, but you’ll see warnings when unused code accumulates.
Exit Codes Control Hook Behavior
Understanding exit codes gives you fine-grained control:
- Exit 0: Success. No output shown to Claude.
- Exit 1: Failure. Blocks the operation and shows the error.
- Exit 2: Always shows output in chat, regardless of success or failure.
Use this for conditional handling:
| |
When formatting fails (usually a syntax error from Claude), the hook blocks and you see exactly what went wrong.
The Recommended Minimal Setup
If you want maximum impact with minimal complexity, start with just two hooks:
| |
This covers the essentials. Add type checking and security scanning once you’ve validated the basic workflow works for your codebase.
When Hooks Don’t Work
Hooks aren’t magic. They break in predictable ways:
Slow hooks kill productivity. If your test suite takes 5 minutes, running it after every edit is impractical. Either run it in the background (accepting delayed feedback) or limit the hook to fast unit tests.
Conflicting tools cause chaos. If you have both isort and Black running, and they disagree on import formatting, you get an infinite loop of reformatting. Use isort --profile black to prevent this.
CI/CD should remain the authority. Hooks are for fast feedback during development. Your CI pipeline should run the full test suite, security scans, and comprehensive type checking. Hooks complement CI; they don’t replace it.
FAQ
Do hooks work with all Claude Code tools or just edit_file?
edit_file and write_file. You can also use tool_name = "bash" to hook shell commands, but this requires careful configuration to avoid recursion.How do I debug when a hook fails silently?
run_in_background = false. This forces output to appear in the Claude Code interface. Once debugging is complete, revert to your normal configuration.Can I have different hooks for different projects?
.claude/settings.toml within the project directory. These override global hooks from ~/.claude/settings.toml. Use project-specific hooks for project-specific test commands or linting configurations.Will hooks slow down Claude Code significantly?
What happens if a hook command isn't installed?
Conclusion
Key Takeaways
- PostToolUse hooks run automatically after Claude edits Python files, eliminating manual quality checks
- Black + Ruff provides instant formatting and linting in under 100ms per file edit
- Background hooks with
run_in_background = truelet tests run without blocking Claude’s workflow - Exit code 1 blocks operations and surfaces errors; exit code 0 runs silently; exit code 2 always shows output
- Start with just formatting and linting hooks, then add type checking and security scanning
- Project-specific hooks in
.claude/settings.tomloverride global hooks for per-project customization - Hooks complement CI/CD pipelines; they don’t replace comprehensive testing and validation
The best development setup is invisible. You shouldn’t be thinking about running formatters or linters—that’s what hooks are for. Configure them once, and every Python file Claude touches gets automatically validated. The result is cleaner code, fewer review cycles, and less time spent on the mechanical parts of development.