| name | git-hooks-automation |
| description | Sets up versioned Git hooks with Husky + lint-staged, the Python pre-commit framework, or core.hooksPath: pre-commit lint/format, commitlint, pre-push tests. Use when the user mentions pre-commit, commit-msg, Husky, lint-staged, commitlint, or githooks. Not for day-to-day git commit/branch/rebase/PR (git-workflow) or CI pipeline YAML unrelated to local hooks. |
| version | 1.0.1 |
| risk | safe |
| source | community |
| date_added | 2026-03-07 |
Git Hooks Automation
Automate code quality enforcement at the Git level. Set up hooks that lint, format, test, and validate before commits and pushes ever reach your CI pipeline — catching issues in seconds instead of minutes.
When to Use
- User asks to "set up git hooks" or "add pre-commit hooks"
- Configuring Husky, lint-staged, or the pre-commit framework
- Enforcing commit message conventions (Conventional Commits, commitlint)
- Automating linting, formatting, or type-checking before commits
- Setting up pre-push hooks for test runners
- Migrating from Husky v4 to v9+ or adopting hooks from scratch
- User mentions "pre-commit", "commit-msg", "pre-push", "lint-staged", or "githooks"
Prerequisites
- Git installed and available on PATH (
git --version to verify)
- For Husky + lint-staged: Node.js 18+ and npm (
node --version, npm --version)
- For pre-commit framework: Python 3.8+ (
python --version)
- An existing Git repository (
git init already run) or willingness to initialize one
- Windows host (primary): PowerShell is the default shell. Use
npx commands directly in PowerShell. For shell-based hook scripts, ensure Git Bash is installed (bundled with Git for Windows). Line endings: configure .gitattributes with * text=auto to avoid CRLF issues in hook scripts.
Procedure
1. Understand Git Hook Types
Git hooks are scripts that run automatically at specific points in the Git workflow. They live in .git/hooks/ and are not version-controlled by default — which is why tools like Husky exist.
| Hook | Fires When | Common Use |
|---|
pre-commit | Before commit is created | Lint, format, type-check staged files |
prepare-commit-msg | After default msg, before editor | Auto-populate commit templates |
commit-msg | After user writes commit message | Enforce commit message format |
post-commit | After commit is created | Notifications, logging |
pre-push | Before push to remote | Run tests, check branch policies |
pre-rebase | Before rebase starts | Prevent rebase on protected branches |
post-merge | After merge completes | Install deps, run migrations |
post-checkout | After checkout/switch | Install deps, rebuild assets |
2. Choose Your Approach
| Approach | Best For | Language |
|---|
| Husky + lint-staged | JavaScript/TypeScript projects | Node.js |
| pre-commit framework | Python or polyglot projects | Any |
Custom shell hooks + core.hooksPath | No Node/Python, or full control | Any |
3. Husky + lint-staged (Node.js Projects)
The modern standard for JavaScript/TypeScript projects. Husky manages Git hooks; lint-staged runs commands only on staged files for speed.
3a. Install and Initialize (Husky v9+)
npm install --save-dev husky lint-staged
npx husky init
echo "npx lint-staged" > .husky/pre-commit
Windows (PowerShell): The echo command above works in Git Bash. In PowerShell, use:
Set-Content -Path .husky\pre-commit -Value "npx lint-staged" -NoNewline
Or simply open .husky\pre-commit in your editor and type the command.
3b. Configure lint-staged in package.json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix --max-warnings=0",
"prettier --write"
],
"*.{css,scss}": [
"prettier --write",
"stylelint --fix"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
}
}
3c. Add Commit Message Linting with commitlint
npm install --save-dev @commitlint/cli @commitlint/config-conventional
cat > commitlint.config.js << 'EOF'
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', [
'feat', 'fix', 'docs', 'style', 'refactor',
'perf', 'test', 'build', 'ci', 'chore', 'revert'
]],
'subject-max-length': [2, 'always', 72],
'body-max-line-length': [2, 'always', 100]
}
};
EOF
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
Windows (PowerShell): Use Set-Content for the commit-msg hook:
Set-Content -Path .husky\commit-msg -Value "npx --no -- commitlint --edit `$1" -NoNewline
3d. Add Pre-Push Hook
echo "npm test" > .husky/pre-push
3e. Verify Directory Structure
project/
├── .husky/
│ ├── pre-commit # npx lint-staged
│ ├── commit-msg # npx --no -- commitlint --edit $1
│ └── pre-push # npm test
├── commitlint.config.js
├── package.json # lint-staged config here
└── ...
4. pre-commit Framework (Python / Polyglot)
Language-agnostic framework that works with any project. Hooks are defined in YAML and run in isolated environments.
4a. Install and Create Config
pip install pre-commit
cat > .pre-commit-config.yaml << 'EOF'
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-added-large-files
args: ['--maxkb=500']
- id: check-merge-conflict
- id: detect-private-key
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
hooks:
- id: ruff
args: ['--fix']
- id: ruff-format
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.10.0.1
hooks:
- id: shellcheck
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.2.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
EOF
4b. Install Hooks and Run First Check
pre-commit install
pre-commit install --hook-type commit-msg
pre-commit run --all-files
4c. Key Commands Reference
pre-commit install
pre-commit run --all-files
pre-commit autoupdate
pre-commit run <hook-id>
pre-commit clean
5. Custom Hook Scripts (Any Language)
For projects not using Node or Python, write hooks directly in shell and share them via core.hooksPath.
5a. Create a Portable Pre-Commit Hook
Create .githooks/pre-commit:
#!/bin/sh
set -e
echo "=== Pre-Commit Checks ==="
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "detached")
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
echo "❌ Direct commits to $BRANCH are not allowed. Use a feature branch."
exit 1
fi
if git diff --cached --diff-filter=ACM | grep -nE '(console\.log|debugger|binding\.pry|import pdb)' > /dev/null 2>&1; then
echo "⚠️ Debug statements found in staged files:"
git diff --cached --diff-filter=ACM | grep -nE '(console\.log|debugger|binding\.pry|import pdb)'
echo "Remove them or use git commit --no-verify to bypass."
exit 1
fi
LARGE_FILES=$(git diff --cached --name-only --diff-filter=ACM | while read f; do
size=$(wc -c < "$f" 2>/dev/null || echo 0)
if [ "$size" -gt 1048576 ]; then echo "$f ()KB)";
)
[ -n ];
1
git diff --cached --diff-filter=ACM | grep -nEi > /dev/null 2>&1;
1
5b. Share Custom Hooks via core.hooksPath
git config core.hooksPath .githooks
setup:
git config core.hooksPath .githooks
chmod +x .githooks/*
Windows (PowerShell): chmod is not available in PowerShell. Use Git Bash for the chmod +x step, or rely on core.hooksPath which does not require the executable bit on Windows.
6. CI Integration
Hooks are a first line of defense, but CI is the source of truth.
6a. Run pre-commit in CI (GitHub Actions)
name: Lint
on: [push, pull_request]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: pre-commit/action@v3.0.1
6b. Run lint-staged Validation in CI
name: Lint Check
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx eslint . --max-warnings=0
- run: npx prettier --check .
7. Migration: Husky v4 → v9+
npm uninstall husky
rm -rf .husky
npm install --save-dev husky
npx husky init
echo "npx lint-staged" > .husky/pre-commit
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
8. Gradual Adoption Strategy
For existing projects, adopt hooks incrementally to reduce team friction:
- Week 1: Start with formatting only —
{ "*.{js,ts}": ["prettier --write"] }
- Week 2-3: Add linting —
{ "*.{js,ts}": ["eslint --fix", "prettier --write"] }
- Week 4: Add commit message linting with commitlint
- Week 5+: Add pre-push test runner
Pitfalls
Hooks Not Running
| Symptom | Cause | Fix |
|---|
| Hooks silently skipped | Not installed in .git/hooks/ | Run npx husky init or pre-commit install |
| "Permission denied" | Hook file not executable | chmod +x .husky/pre-commit (Git Bash on Windows) |
| Hooks run but wrong ones | Stale hooks from old setup | Delete .git/hooks/ contents, reinstall |
| Works locally, fails in CI | Different Node/Python versions | Pin versions in CI config |
Performance Issues
{
"scripts": {
"precommit": "eslint src/ && prettier --write src/"
}
}
{
"lint-staged": {
"*.{js,ts}": ["eslint --fix", "prettier --write"]
}
}
Bypassing Hooks (When Needed)
git commit --no-verify -m "wip: quick save"
git push --no-verify
SKIP=eslint git commit -m "fix: update config"
Warning: Bypassing hooks should be rare. If your team frequently bypasses, the hooks are too slow or too strict — fix the hooks, not the workflow.
Windows-Specific Pitfalls
- CRLF line endings: Hook scripts with CRLF endings fail silently on some Git versions. Add
* text=auto to .gitattributes and run git add --renormalize .
- PowerShell vs Git Bash: Husky hooks execute via Git Bash, not PowerShell. Shell scripts in
.husky/ must use POSIX syntax, not PowerShell cmdlets.
$1 in commit-msg: In PowerShell, backtick-escape the dollar sign when writing the file: `$1. In Git Bash, use \$1.
Key Principles
- Staged files only — Never lint the entire codebase on every commit
- Auto-fix when possible —
--fix flags reduce developer friction
- Fast hooks — Pre-commit should complete in < 5 seconds
- Fail loud — Clear error messages with actionable fixes
- Team-shared — Use Husky or
core.hooksPath so hooks are version-controlled
- CI as backup — Hooks are convenience; CI is the enforcer
- Gradual adoption — Start with formatting, add linting, then testing
Verification
Verify Husky + lint-staged Setup
ls -la .husky/
cat package.json | grep -A 10 "lint-staged"
echo "// test" >> src/index.js
git add src/index.js
git commit -m "test: verify hooks"
git commit -m "bad message format" --allow-empty
git commit -m "feat: valid conventional commit" --allow-empty
Verify pre-commit Framework Setup
pre-commit install
pre-commit run --all-files
pre-commit install --hook-type commit-msg
git commit -m "bad message" --allow-empty
git commit -m "fix: valid message" --allow-empty
Verify Custom core.hooksPath Setup
git config core.hooksPath
ls -la .githooks/
git commit --allow-empty -m "test: verify custom hooks"
Related Skills
codebase-audit-pre-push — Deep audit before GitHub push
verification-before-completion — Verification before claiming work is done
bash-pro — Advanced shell scripting for custom hooks
github-actions-templates — CI/CD workflow templates
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.