Why commits get blocked
A Git pre-commit hook runs before a commit is created. If any hook exits with a non-zero status, Git aborts the commit and shows “pre-commit hook failed.” This is intended to prevent bad code, formatting drift, or secrets from entering the repo.
This guide shows how to diagnose failures quickly, fix the underlying issues, and, when necessary, bypass safely.
Quickstart: unblock a failing commit safely
- Read the last lines of the hook output to identify the failing tool or check.
- Run the failing hook or command locally as the same user and environment.
- Apply the suggested fixes (often formatting or linting), re-stage changes, and commit again.
- If you use the pre-commit framework (Python tool), run:
- pre-commit install
- pre-commit run --all-files
- pre-commit autoupdate (if versions are stale)
- As a last resort only, bypass temporarily with:
- git commit --no-verify
Minimal working example (using the pre-commit framework)
This example installs two basic checks that frequently cause failures: trailing whitespace and missing EOF newline.
- Create the config at the repo root:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- Install and run:
# install the framework (one-time per machine)
pipx install pre-commit || pip install --user pre-commit
# register git hooks for this repo
pre-commit install
# run on all files to warm caches and fix existing issues
pre-commit run --all-files
# make a change, stage, and commit; hooks will run automatically
git add -A && git commit -m "test hooks"
If the hooks modify files (e.g., fix whitespace), you must re-stage and commit again:
git add -A && git commit -m "apply pre-commit fixes"
Diagnose and fix: step-by-step
- Capture the exact error
- Re-run your commit to read the output:
- git commit -m "msg"
- Note the failing hook name and the command it ran.
- Reproduce the failure manually
- If using the pre-commit framework:
- pre-commit run # runs on staged files
- pre-commit run --all-files
- pre-commit run <hook-id> --all-files
- If using a custom shell hook at .git/hooks/pre-commit:
- bash -x .git/hooks/pre-commit # run with tracing
- Fix file issues, then re-stage
- Many failures are from formatters/linters changing files.
- Apply the suggested fixes or auto-fix:
- pre-commit run --all-files
- or run the specific tool (e.g., black, eslint, gofmt), then:
- git add -A
- Verify hook executability and line endings (custom hooks)
- Make the hook executable:
- chmod +x .git/hooks/pre-commit
- Ensure a valid shebang on the first line (e.g., #!/usr/bin/env bash or python3).
- Fix CRLF issues on Windows that break shebang execution:
- git config core.autocrlf input
- or convert: dos2unix .git/hooks/pre-commit
- Ensure the hook is installed where Git expects
- Check custom hooks path:
- git config --get core.hooksPath # empty means .git/hooks
- If using pre-commit framework:
- pre-commit install # regenerates the .git/hooks/pre-commit shim
- Install missing runtimes and dependencies
- Errors like “command not found” indicate missing tools.
- Make sure Node, Python, Go, or other runtimes are on PATH inside non-interactive shells.
- For pre-commit-managed hooks, environments are auto-created; if corrupted:
- pre-commit clean && pre-commit run --all-files
- Update stale hooks and pin versions
- For pre-commit:
- pre-commit autoupdate
- Commit the updated .pre-commit-config.yaml and re-run.
- When to bypass
- Use only for emergencies (e.g., broken dev environment, CI covers the check):
- git commit --no-verify
- Follow up by fixing the hook and making a proper commit.
Common errors and how to fix them
| Symptom | Likely cause | Fix |
|---|---|---|
| pre-commit: command not found | Framework not installed | pipx install pre-commit or pip install --user pre-commit, then pre-commit install |
| fatal: cannot run .git/hooks/pre-commit: No such file or directory | Wrong hooks path or missing script | Check git config core.hooksPath; run pre-commit install or restore .git/hooks/pre-commit |
| Permission denied | Hook not executable | chmod +x .git/hooks/pre-commit |
| /usr/bin/env: bash | ||
| : No such file or directory | CRLF line endings | Convert to LF (dos2unix) and set core.autocrlf appropriately |
| Hook failed: files were modified by this hook | Auto-fixer changed files | git add -A then commit again; or run pre-commit run --all-files |
| Tool exits with non-zero (linter errors) | Code issue or config mismatch | Fix code or align tool config; re-run the hook |
| Works locally, fails in CI | Different PATH/runtime, missing deps | Install runtimes in CI; cache pre-commit; run pre-commit run --all-files in CI |
Pitfalls to avoid
- Assuming hooks run on unstaged content; most run on staged snapshots. Re-stage after fixes.
- Relying on interactive shells. Hooks run non-interactively, so pyenv/nvm/asdf shims may not load; set PATH explicitly or use absolute paths.
- Having multiple hook systems (e.g., Husky and pre-commit) both bound to pre-commit; they can conflict.
- Ignoring hook versions; unpinned or stale versions cause drift across machines.
- Running heavyweight checks in pre-commit that scan the whole repo; prefer pre-push or CI for slow tasks.
Performance notes
- Run only on changed files. Pre-commit does this by default; avoid --all-files in normal workflow.
- Limit scope using include/exclude patterns in .pre-commit-config.yaml to skip vendor or generated code.
- Prefer auto-fixers over pure linters to minimize re-runs.
- Cache environments in CI. Persist ~/.cache/pre-commit (or the platform cache dir) between builds.
- Split heavy checks to pre-push or CI. Keep pre-commit fast (<2s) to maintain developer productivity.
- Batch file I/O: tools like eslint and black support parallelism; configure them accordingly.
Tiny FAQ
How do I bypass a hook?
- git commit --no-verify. Use sparingly and fix the root cause promptly.
How do I run only one hook?
- pre-commit run <hook-id> --all-files or pre-commit run <hook-id>.
How do I skip a specific hook temporarily?
- Use environment variable: SKIP=<hook-id> git commit -m "msg".
Hooks don’t run at all. Why?
- Not installed (pre-commit install), wrong core.hooksPath, or missing .git directory.
How do I debug a custom shell hook?
- Add set -euxo pipefail at the top and run bash -x .git/hooks/pre-commit.
Should I commit after auto-fixes?
- Yes. Re-stage modified files and commit; that’s the intended flow.
Summary
- Read the error, 2) reproduce locally, 3) fix and re-stage, 4) ensure hooks are installed and executable, 5) keep hooks fast and up to date, 6) bypass only when necessary.