I use pre-commit hooks to catch problems before they enter my codebase. They run when I commit code, stopping bad code at the source. They:
- Format code
- Find syntax errors
- Check for security issues
- Run tests
- Enforce style rules
Hooks catch issues when they’re cheap to fix - on my machine, before anyone sees them.
My monorepo holds many projects in one place. Each needs different checks:
- Frontend needs ESLint
- Backend needs Black
- Shared code needs docs checks
Multiple hooks let me run the right checks on the right files. My React code doesn’t need Python linting. My Python code doesn’t need JavaScript tests.
I use pre-commit to manage my hooks. It’s a Python package that lets me install and configure hooks easily.
The problem with sub-projects
To keep things clean, each sub-project has its own .pre-commit-config.yaml file.
This creates a challenge: pre-commit run —all-files only runs hooks defined in the current directory’s config file. It won’t find or run hooks in sub-project config files.
If I run this command from the root, it ignores all my sub-project hooks. This means code can slip through unchecked.
I solved this by adding “router hooks” to my root config file:
- repo: local hooks: - id: backend-hooks name: Backend Hooks entry: bash -c 'cd packages/backend && pre-commit run && cd -' language: system files: ^packages/backend/ pass_filenames: false
This hook:
- Activates when backend files change
- Changes directory to the backend folder
- Runs that folder’s own pre-commit config
- Returns to the root directory
The cd - command is crucial. Without it, Git creates lock files it can’t release. This happens because Git keeps its index in the root .git directory.
Why pass_filenames: false is needed
The pass_filenames: false option is important. When Git triggers a hook, it passes file paths relative to the root. But after changing to a sub-directory, these paths become invalid.
By turning off filename passing, the sub-project’s pre-commit run scans all files in that directory instead.
Results
This setup lets me:
- Keep separate hook configs for each project
- Run everything from the root with one command
- Check only relevant files for each project
- Avoid Git lock errors
Each project keeps its own rules, but I control them all from one place.