| name | git-hooks-automation |
| description | Master Git hooks setup with Husky, lint-staged, pre-commit framework, and commitlint. Automate code quality gates, formatting, linting, and commit message enforcement before code reaches CI. |
| 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 This Skill
- 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"
Git Hooks Fundamentals
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 Types & When They Fire
| 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 |
Native Git Hooks (No Framework)
cat > .git/hooks/pre-commit << 'EOF'
set -e
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|jsx|tsx)$' || true)
if [ -n "$STAGED_FILES" ]; then
echo "🔍 Linting staged files..."
echo "$STAGED_FILES" | xargs npx eslint --fix
echo "$STAGED_FILES" | xargs git add
fi
EOF
chmod +x .git/hooks/pre-commit
Problem: .git/hooks/ is local-only and not shared with the team. Use a framework instead.
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.
Quick Setup (Husky v9+)
npm install --save-dev husky lint-staged
npx husky init
echo "npx lint-staged" > .husky/pre-commit
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"
]
}
}
Add Commit Message Linting
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
Add Pre-Push Hook
echo "npm test" > .husky/pre-push
Complete Husky 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
└── ...
pre-commit Framework (Python / Polyglot)
Language-agnostic framework that works with any project. Hooks are defined in YAML and run in isolated environments.
Setup
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
pre-commit install
pre-commit install --hook-type commit-msg
pre-commit run --all-files
Key Commands
pre-commit install
pre-commit run --all-files
pre-commit autoupdate
pre-commit run <hook-id>
pre-commit clean
Custom Hook Scripts (Any Language)
For projects not using Node or Python, write hooks directly in shell.
Portable Pre-Commit Hook
#!/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 ($((size/1024))KB)"; fi
done)
if [ -n "$LARGE_FILES" ]; then
echo "❌ Large files detected:"
echo "$LARGE_FILES"
exit 1
fi
if git diff --cached --diff-filter=ACM | grep -nEi '(AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9]{48}|ghp_[a-zA-Z0-9]{36}|password\s*=\s*["\x27][^"\x27]+["\x27])' > /dev/null 2>&1; then
echo "🚨 Potential secrets detected in staged changes! Review before committing."
exit 1
fi
echo "✅ All pre-commit checks passed"
Share Custom Hooks via core.hooksPath
git config core.hooksPath .githooks
setup:
git config core.hooksPath .githooks
chmod +x .githooks/*
CI Integration
Hooks are a first line of defense, but CI is the source of truth.
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
Run lint-staged in CI (Validation Only)
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 .
Common Pitfalls & Fixes
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 |
| 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 them.
Migration Guide
Husky v4 → v9 Migration
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
Adopting Hooks on an Existing Project
{ "*.{js,ts}": ["prettier --write"] }
{ "*.{js,ts}": ["eslint --fix", "prettier --write"] }
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
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.