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 adopti…

|| true)\n\nif [ -n \"$STAGED_FILES\" ]; then\n echo \"🔍 Linting staged files...\"\n echo \"$STAGED_FILES\" | xargs npx eslint --fix\n echo \"$STAGED_FILES\" | xargs git add # Re-stage after fixes\nfi\nEOF\nchmod +x .git/hooks/pre-commit\n```\n\n**Problem**: `.git/hooks/` is local-only and not shared with the team. Use a framework instead.\n\n## Husky + lint-staged (Node.js Projects)\n\nThe modern standard for JavaScript/TypeScript projects. Husky manages Git hooks; lint-staged runs commands only on staged files for speed.\n\n### Quick Setup (Husky v9+)\n\n```bash\n# Install\nnpm install --save-dev husky lint-staged\n\n# Initialize Husky (creates .husky/ directory)\nnpx husky init\n\n# The init command creates a pre-commit hook — edit it:\necho \"npx lint-staged\" > .husky/pre-commit\n```\n\n### Configure lint-staged in `package.json`\n\n```json\n{\n \"lint-staged\": {\n \"*.{js,jsx,ts,tsx}\": [\n \"eslint --fix --max-warnings=0\",\n \"prettier --write\"\n ],\n \"*.{css,scss}\": [\n \"prettier --write\",\n \"stylelint --fix\"\n ],\n \"*.{json,md,yml,yaml}\": [\n \"prettier --write\"\n ]\n }\n}\n```\n\n### Add Commit Message Linting\n\n```bash\n# Install commitlint\nnpm install --save-dev @commitlint/cli @commitlint/config-conventional\n\n# Create commitlint config\ncat > commitlint.config.js \u003c\u003c 'EOF'\nmodule.exports = {\n extends: ['@commitlint/config-conventional'],\n rules: {\n 'type-enum': [2, 'always', [\n 'feat', 'fix', 'docs', 'style', 'refactor',\n 'perf', 'test', 'build', 'ci', 'chore', 'revert'\n ]],\n 'subject-max-length': [2, 'always', 72],\n 'body-max-line-length': [2, 'always', 100]\n }\n};\nEOF\n\n# Add commit-msg hook\necho \"npx --no -- commitlint --edit \\$1\" > .husky/commit-msg\n```\n\n### Add Pre-Push Hook\n\n```bash\n# Run tests before pushing\necho \"npm test\" > .husky/pre-push\n```\n\n### Complete Husky Directory Structure\n\n```\nproject/\n├── .husky/\n│ ├── pre-commit # npx lint-staged\n│ ├── commit-msg # npx --no -- commitlint --edit $1\n│ └── pre-push # npm test\n├── commitlint.config.js\n├── package.json # lint-staged config here\n└── ...\n```\n\n## pre-commit Framework (Python / Polyglot)\n\nLanguage-agnostic framework that works with any project. Hooks are defined in YAML and run in isolated environments.\n\n### Setup\n\n```bash\n# Install (Python required)\npip install pre-commit\n\n# Create config\ncat > .pre-commit-config.yaml \u003c\u003c 'EOF'\nrepos:\n # Built-in checks\n - repo: https://github.com/pre-commit/pre-commit-hooks\n rev: v4.6.0\n hooks:\n - id: trailing-whitespace\n - id: end-of-file-fixer\n - id: check-yaml\n - id: check-json\n - id: check-added-large-files\n args: ['--maxkb=500']\n - id: check-merge-conflict\n - id: detect-private-key\n\n # Python formatting\n - repo: https://github.com/psf/black\n rev: 24.4.2\n hooks:\n - id: black\n\n # Python linting\n - repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v0.4.4\n hooks:\n - id: ruff\n args: ['--fix']\n - id: ruff-format\n\n # Shell script linting\n - repo: https://github.com/shellcheck-py/shellcheck-py\n rev: v0.10.0.1\n hooks:\n - id: shellcheck\n\n # Commit message format\n - repo: https://github.com/compilerla/conventional-pre-commit\n rev: v3.2.0\n hooks:\n - id: conventional-pre-commit\n stages: [commit-msg]\nEOF\n\n# Install hooks into .git/hooks/\npre-commit install\npre-commit install --hook-type commit-msg\n\n# Run against all files (first time)\npre-commit run --all-files\n```\n\n### Key Commands\n\n```bash\npre-commit install # Install hooks\npre-commit run --all-files # Run on everything (CI or first setup)\npre-commit autoupdate # Update hook versions\npre-commit run \u003chook-id> # Run a specific hook\npre-commit clean # Clear cached environments\n```\n\n## Custom Hook Scripts (Any Language)\n\nFor projects not using Node or Python, write hooks directly in shell.\n\n### Portable Pre-Commit Hook\n\n```bash\n#!/bin/sh\n# .githooks/pre-commit — Team-shared hooks directory\nset -e\n\necho \"=== Pre-Commit Checks ===\"\n\n# 1. Prevent commits to main/master\nBRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo \"detached\")\nif [ \"$BRANCH\" = \"main\" ] || [ \"$BRANCH\" = \"master\" ]; then\n echo \"❌ Direct commits to $BRANCH are not allowed. Use a feature branch.\"\n exit 1\nfi\n\n# 2. Check for debugging artifacts\nif git diff --cached --diff-filter=ACM | grep -nE '(console\\.log|debugger|binding\\.pry|import pdb)' > /dev/null 2>&1; then\n echo \"⚠️ Debug statements found in staged files:\"\n git diff --cached --diff-filter=ACM | grep -nE '(console\\.log|debugger|binding\\.pry|import pdb)'\n echo \"Remove them or use git commit --no-verify to bypass.\"\n exit 1\nfi\n\n# 3. Check for large files (>1MB)\nLARGE_FILES=$(git diff --cached --name-only --diff-filter=ACM | while read f; do\n size=$(wc -c \u003c \"$f\" 2>/dev/null || echo 0)\n if [ \"$size\" -gt 1048576 ]; then echo \"$f ($((size/1024))KB)\"; fi\ndone)\nif [ -n \"$LARGE_FILES\" ]; then\n echo \"❌ Large files detected:\"\n echo \"$LARGE_FILES\"\n exit 1\nfi\n\n# 4. Check for secrets patterns\nif 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\n echo \"🚨 Potential secrets detected in staged changes! Review before committing.\"\n exit 1\nfi\n\necho \"✅ All pre-commit checks passed\"\n```\n\n### Share Custom Hooks via `core.hooksPath`\n\n```bash\n# In your repo, set a shared hooks directory\ngit config core.hooksPath .githooks\n\n# Add to project setup docs or Makefile\n# Makefile\nsetup:\n\tgit config core.hooksPath .githooks\n\tchmod +x .githooks/*\n```\n\n## CI Integration\n\nHooks are a first line of defense, but CI is the source of truth.\n\n### Run pre-commit in CI (GitHub Actions)\n\n```yaml\n# .github/workflows/lint.yml\nname: Lint\non: [push, pull_request]\njobs:\n pre-commit:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - uses: pre-commit/[email protected]\n```\n\n### Run lint-staged in CI (Validation Only)\n\n```yaml\n# Validate that lint-staged would pass (catch bypassed hooks)\nname: Lint Check\non: [pull_request]\njobs:\n lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: 20\n - run: npm ci\n - run: npx eslint . --max-warnings=0\n - run: npx prettier --check .\n```\n\n## Common Pitfalls & Fixes\n\n### Hooks Not Running\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Hooks silently skipped | Not installed in `.git/hooks/` | Run `npx husky init` or `pre-commit install` |\n| \"Permission denied\" | Hook file not executable | `chmod +x .husky/pre-commit` |\n| Hooks run but wrong ones | Stale hooks from old setup | Delete `.git/hooks/` contents, reinstall |\n| Works locally, fails in CI | Different Node/Python versions | Pin versions in CI config |\n\n### Performance Issues\n\n```json\n// ❌ Slow: runs on ALL files every commit\n{\n \"scripts\": {\n \"precommit\": \"eslint src/ && prettier --write src/\"\n }\n}\n\n// ✅ Fast: lint-staged runs ONLY on staged files\n{\n \"lint-staged\": {\n \"*.{js,ts}\": [\"eslint --fix\", \"prettier --write\"]\n }\n}\n```\n\n### Bypassing Hooks (When Needed)\n\n```bash\n# Skip all hooks for a single commit\ngit commit --no-verify -m \"wip: quick save\"\n\n# Skip pre-push only\ngit push --no-verify\n\n# Skip specific pre-commit hooks\nSKIP=eslint git commit -m \"fix: update config\"\n```\n\n> **Warning**: Bypassing hooks should be rare. If your team frequently bypasses, the hooks are too slow or too strict — fix them.\n\n## Migration Guide\n\n### Husky v4 → v9 Migration\n\n```bash\n# 1. Remove old Husky\nnpm uninstall husky\nrm -rf .husky\n\n# 2. Remove old config from package.json\n# Delete \"husky\": { \"hooks\": { ... } } section\n\n# 3. Install fresh\nnpm install --save-dev husky\nnpx husky init\n\n# 4. Recreate hooks\necho \"npx lint-staged\" > .husky/pre-commit\necho \"npx --no -- commitlint --edit \\$1\" > .husky/commit-msg\n\n# 5. Clean up — old Husky used package.json config,\n# new Husky uses .husky/ directory with plain scripts\n```\n\n### Adopting Hooks on an Existing Project\n\n```bash\n# Step 1: Start with formatting only (low friction)\n# lint-staged config:\n{ \"*.{js,ts}\": [\"prettier --write\"] }\n\n# Step 2: Add linting after team adjusts (1-2 weeks later)\n{ \"*.{js,ts}\": [\"eslint --fix\", \"prettier --write\"] }\n\n# Step 3: Add commit message linting\n# Step 4: Add pre-push test runner\n\n# Gradual adoption prevents team resistance\n```\n\n## Key Principles\n\n- **Staged files only** — Never lint the entire codebase on every commit\n- **Auto-fix when possible** — `--fix` flags reduce developer friction\n- **Fast hooks** — Pre-commit should complete in \u003c 5 seconds\n- **Fail loud** — Clear error messages with actionable fixes\n- **Team-shared** — Use Husky or `core.hooksPath` so hooks are version-controlled\n- **CI as backup** — Hooks are convenience; CI is the enforcer\n- **Gradual adoption** — Start with formatting, add linting, then testing\n\n## Related Skills\n\n- `@codebase-audit-pre-push` - Deep audit before GitHub push\n- `@verification-before-completion` - Verification before claiming work is done\n- `@bash-pro` - Advanced shell scripting for custom hooks\n- `@github-actions-templates` - CI/CD workflow templates\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n---","attachment_filenames":[],"attachments":[],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Git Hooks Automation","type":"text"}]},{"type":"paragraph","content":[{"text":"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.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use This Skill","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User asks to \"set up git hooks\" or \"add pre-commit hooks\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Configuring Husky, lint-staged, or the pre-commit framework","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Enforcing commit message conventions (Conventional Commits, commitlint)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Automating linting, formatting, or type-checking before commits","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Setting up pre-push hooks for test runners","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Migrating from Husky v4 to v9+ or adopting hooks from scratch","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User mentions \"pre-commit\", \"commit-msg\", \"pre-push\", \"lint-staged\", or \"githooks\"","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Git Hooks Fundamentals","type":"text"}]},{"type":"paragraph","content":[{"text":"Git hooks are scripts that run automatically at specific points in the Git workflow. They live in ","type":"text"},{"text":".git/hooks/","type":"text","marks":[{"type":"code_inline"}]},{"text":" and are not version-controlled by default — which is why tools like Husky exist.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Hook Types & When They Fire","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Hook","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fires When","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Common Use","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pre-commit","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Before commit is created","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Lint, format, type-check staged files","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"prepare-commit-msg","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"After default msg, before editor","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Auto-populate commit templates","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"commit-msg","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"After user writes commit message","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Enforce commit message format","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"post-commit","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"After commit is created","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Notifications, logging","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pre-push","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Before push to remote","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run tests, check branch policies","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pre-rebase","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Before rebase starts","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prevent rebase on protected branches","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"post-merge","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"After merge completes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Install deps, run migrations","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"post-checkout","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"After checkout/switch","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Install deps, rebuild assets","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Native Git Hooks (No Framework)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Create a pre-commit hook manually\ncat > .git/hooks/pre-commit \u003c\u003c 'EOF'\n#!/bin/sh\nset -e\n\n# Run linter on staged files only\nSTAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.(js|ts|jsx|tsx)

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 adopti…

|| true)\n\nif [ -n \"$STAGED_FILES\" ]; then\n echo \"🔍 Linting staged files...\"\n echo \"$STAGED_FILES\" | xargs npx eslint --fix\n echo \"$STAGED_FILES\" | xargs git add # Re-stage after fixes\nfi\nEOF\nchmod +x .git/hooks/pre-commit","type":"text"}]},{"type":"paragraph","content":[{"text":"Problem","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":".git/hooks/","type":"text","marks":[{"type":"code_inline"}]},{"text":" is local-only and not shared with the team. Use a framework instead.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Husky + lint-staged (Node.js Projects)","type":"text"}]},{"type":"paragraph","content":[{"text":"The modern standard for JavaScript/TypeScript projects. Husky manages Git hooks; lint-staged runs commands only on staged files for speed.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Quick Setup (Husky v9+)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Install\nnpm install --save-dev husky lint-staged\n\n# Initialize Husky (creates .husky/ directory)\nnpx husky init\n\n# The init command creates a pre-commit hook — edit it:\necho \"npx lint-staged\" > .husky/pre-commit","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Configure lint-staged in ","type":"text"},{"text":"package.json","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"lint-staged\": {\n \"*.{js,jsx,ts,tsx}\": [\n \"eslint --fix --max-warnings=0\",\n \"prettier --write\"\n ],\n \"*.{css,scss}\": [\n \"prettier --write\",\n \"stylelint --fix\"\n ],\n \"*.{json,md,yml,yaml}\": [\n \"prettier --write\"\n ]\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Add Commit Message Linting","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Install commitlint\nnpm install --save-dev @commitlint/cli @commitlint/config-conventional\n\n# Create commitlint config\ncat > commitlint.config.js \u003c\u003c 'EOF'\nmodule.exports = {\n extends: ['@commitlint/config-conventional'],\n rules: {\n 'type-enum': [2, 'always', [\n 'feat', 'fix', 'docs', 'style', 'refactor',\n 'perf', 'test', 'build', 'ci', 'chore', 'revert'\n ]],\n 'subject-max-length': [2, 'always', 72],\n 'body-max-line-length': [2, 'always', 100]\n }\n};\nEOF\n\n# Add commit-msg hook\necho \"npx --no -- commitlint --edit \\$1\" > .husky/commit-msg","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Add Pre-Push Hook","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Run tests before pushing\necho \"npm test\" > .husky/pre-push","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Complete Husky Directory Structure","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"project/\n├── .husky/\n│ ├── pre-commit # npx lint-staged\n│ ├── commit-msg # npx --no -- commitlint --edit $1\n│ └── pre-push # npm test\n├── commitlint.config.js\n├── package.json # lint-staged config here\n└── ...","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"pre-commit Framework (Python / Polyglot)","type":"text"}]},{"type":"paragraph","content":[{"text":"Language-agnostic framework that works with any project. Hooks are defined in YAML and run in isolated environments.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Setup","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Install (Python required)\npip install pre-commit\n\n# Create config\ncat > .pre-commit-config.yaml \u003c\u003c 'EOF'\nrepos:\n # Built-in checks\n - repo: https://github.com/pre-commit/pre-commit-hooks\n rev: v4.6.0\n hooks:\n - id: trailing-whitespace\n - id: end-of-file-fixer\n - id: check-yaml\n - id: check-json\n - id: check-added-large-files\n args: ['--maxkb=500']\n - id: check-merge-conflict\n - id: detect-private-key\n\n # Python formatting\n - repo: https://github.com/psf/black\n rev: 24.4.2\n hooks:\n - id: black\n\n # Python linting\n - repo: https://github.com/astral-sh/ruff-pre-commit\n rev: v0.4.4\n hooks:\n - id: ruff\n args: ['--fix']\n - id: ruff-format\n\n # Shell script linting\n - repo: https://github.com/shellcheck-py/shellcheck-py\n rev: v0.10.0.1\n hooks:\n - id: shellcheck\n\n # Commit message format\n - repo: https://github.com/compilerla/conventional-pre-commit\n rev: v3.2.0\n hooks:\n - id: conventional-pre-commit\n stages: [commit-msg]\nEOF\n\n# Install hooks into .git/hooks/\npre-commit install\npre-commit install --hook-type commit-msg\n\n# Run against all files (first time)\npre-commit run --all-files","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Key Commands","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"pre-commit install # Install hooks\npre-commit run --all-files # Run on everything (CI or first setup)\npre-commit autoupdate # Update hook versions\npre-commit run \u003chook-id> # Run a specific hook\npre-commit clean # Clear cached environments","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Custom Hook Scripts (Any Language)","type":"text"}]},{"type":"paragraph","content":[{"text":"For projects not using Node or Python, write hooks directly in shell.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Portable Pre-Commit Hook","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"#!/bin/sh\n# .githooks/pre-commit — Team-shared hooks directory\nset -e\n\necho \"=== Pre-Commit Checks ===\"\n\n# 1. Prevent commits to main/master\nBRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo \"detached\")\nif [ \"$BRANCH\" = \"main\" ] || [ \"$BRANCH\" = \"master\" ]; then\n echo \"❌ Direct commits to $BRANCH are not allowed. Use a feature branch.\"\n exit 1\nfi\n\n# 2. Check for debugging artifacts\nif git diff --cached --diff-filter=ACM | grep -nE '(console\\.log|debugger|binding\\.pry|import pdb)' > /dev/null 2>&1; then\n echo \"⚠️ Debug statements found in staged files:\"\n git diff --cached --diff-filter=ACM | grep -nE '(console\\.log|debugger|binding\\.pry|import pdb)'\n echo \"Remove them or use git commit --no-verify to bypass.\"\n exit 1\nfi\n\n# 3. Check for large files (>1MB)\nLARGE_FILES=$(git diff --cached --name-only --diff-filter=ACM | while read f; do\n size=$(wc -c \u003c \"$f\" 2>/dev/null || echo 0)\n if [ \"$size\" -gt 1048576 ]; then echo \"$f ($((size/1024))KB)\"; fi\ndone)\nif [ -n \"$LARGE_FILES\" ]; then\n echo \"❌ Large files detected:\"\n echo \"$LARGE_FILES\"\n exit 1\nfi\n\n# 4. Check for secrets patterns\nif 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\n echo \"🚨 Potential secrets detected in staged changes! Review before committing.\"\n exit 1\nfi\n\necho \"✅ All pre-commit checks passed\"","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Share Custom Hooks via ","type":"text"},{"text":"core.hooksPath","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# In your repo, set a shared hooks directory\ngit config core.hooksPath .githooks\n\n# Add to project setup docs or Makefile\n# Makefile\nsetup:\n\tgit config core.hooksPath .githooks\n\tchmod +x .githooks/*","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"CI Integration","type":"text"}]},{"type":"paragraph","content":[{"text":"Hooks are a first line of defense, but CI is the source of truth.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Run pre-commit in CI (GitHub Actions)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"# .github/workflows/lint.yml\nname: Lint\non: [push, pull_request]\njobs:\n pre-commit:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - uses: pre-commit/[email protected]","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Run lint-staged in CI (Validation Only)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"# Validate that lint-staged would pass (catch bypassed hooks)\nname: Lint Check\non: [pull_request]\njobs:\n lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: 20\n - run: npm ci\n - run: npx eslint . --max-warnings=0\n - run: npx prettier --check .","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Pitfalls & Fixes","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Hooks Not Running","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Symptom","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cause","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Hooks silently skipped","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Not installed in ","type":"text"},{"text":".git/hooks/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"npx husky init","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"pre-commit install","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"Permission denied\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Hook file not executable","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"chmod +x .husky/pre-commit","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Hooks run but wrong ones","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stale hooks from old setup","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Delete ","type":"text"},{"text":".git/hooks/","type":"text","marks":[{"type":"code_inline"}]},{"text":" contents, reinstall","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Works locally, fails in CI","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Different Node/Python versions","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pin versions in CI config","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Performance Issues","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"// ❌ Slow: runs on ALL files every commit\n{\n \"scripts\": {\n \"precommit\": \"eslint src/ && prettier --write src/\"\n }\n}\n\n// ✅ Fast: lint-staged runs ONLY on staged files\n{\n \"lint-staged\": {\n \"*.{js,ts}\": [\"eslint --fix\", \"prettier --write\"]\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Bypassing Hooks (When Needed)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Skip all hooks for a single commit\ngit commit --no-verify -m \"wip: quick save\"\n\n# Skip pre-push only\ngit push --no-verify\n\n# Skip specific pre-commit hooks\nSKIP=eslint git commit -m \"fix: update config\"","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Warning","type":"text","marks":[{"type":"strong"}]},{"text":": Bypassing hooks should be rare. If your team frequently bypasses, the hooks are too slow or too strict — fix them.","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Migration Guide","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Husky v4 → v9 Migration","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 1. Remove old Husky\nnpm uninstall husky\nrm -rf .husky\n\n# 2. Remove old config from package.json\n# Delete \"husky\": { \"hooks\": { ... } } section\n\n# 3. Install fresh\nnpm install --save-dev husky\nnpx husky init\n\n# 4. Recreate hooks\necho \"npx lint-staged\" > .husky/pre-commit\necho \"npx --no -- commitlint --edit \\$1\" > .husky/commit-msg\n\n# 5. Clean up — old Husky used package.json config,\n# new Husky uses .husky/ directory with plain scripts","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Adopting Hooks on an Existing Project","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Step 1: Start with formatting only (low friction)\n# lint-staged config:\n{ \"*.{js,ts}\": [\"prettier --write\"] }\n\n# Step 2: Add linting after team adjusts (1-2 weeks later)\n{ \"*.{js,ts}\": [\"eslint --fix\", \"prettier --write\"] }\n\n# Step 3: Add commit message linting\n# Step 4: Add pre-push test runner\n\n# Gradual adoption prevents team resistance","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Key Principles","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Staged files only","type":"text","marks":[{"type":"strong"}]},{"text":" — Never lint the entire codebase on every commit","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Auto-fix when possible","type":"text","marks":[{"type":"strong"}]},{"text":" — ","type":"text"},{"text":"--fix","type":"text","marks":[{"type":"code_inline"}]},{"text":" flags reduce developer friction","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fast hooks","type":"text","marks":[{"type":"strong"}]},{"text":" — Pre-commit should complete in \u003c 5 seconds","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fail loud","type":"text","marks":[{"type":"strong"}]},{"text":" — Clear error messages with actionable fixes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Team-shared","type":"text","marks":[{"type":"strong"}]},{"text":" — Use Husky or ","type":"text"},{"text":"core.hooksPath","type":"text","marks":[{"type":"code_inline"}]},{"text":" so hooks are version-controlled","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CI as backup","type":"text","marks":[{"type":"strong"}]},{"text":" — Hooks are convenience; CI is the enforcer","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Gradual adoption","type":"text","marks":[{"type":"strong"}]},{"text":" — Start with formatting, add linting, then testing","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Related Skills","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@codebase-audit-pre-push","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Deep audit before GitHub push","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@verification-before-completion","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Verification before claiming work is done","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@bash-pro","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Advanced shell scripting for custom hooks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@github-actions-templates","type":"text","marks":[{"type":"code_inline"}]},{"text":" - CI/CD workflow templates","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Limitations","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use this skill only when the task clearly matches the scope described above.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Do not treat the output as a substitute for environment-specific validation, testing, or expert review.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"git-hooks-automation","risk":"safe","author":"@skillopedia","source":{"stars":39376,"repo_name":"antigravity-awesome-skills","origin_url":"https://github.com/sickn33/antigravity-awesome-skills/blob/HEAD/skills/git-hooks-automation/SKILL.md","repo_owner":"sickn33","body_sha256":"a7612b5dc733e725c56415d65e2fece21e3079a200d33bf398dac007f02c3a03","cluster_key":"245379fdb54b392059ea85cd44540d50de656115a2fe39a9c9183a9598705c0e","clean_bundle":{"format":"clean-skill-bundle-v1","source":"sickn33/antigravity-awesome-skills/skills/git-hooks-automation/SKILL.md","bundle_sha256":"c57996bbcfec2f18a70e841edfdd36805deaa5ef5f01a429266781915f7bf48c","attachment_count":0,"text_attachments":0,"binary_attachments":0},"cluster_size":3,"skill_md_path":"skills/git-hooks-automation/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"software-engineering","category_label":"Engineering"},"exact_dupes_collapsed_into_this":2},"version":"v1","category":"software-engineering","date_added":"2026-03-07","import_tag":"clean-skills-v1","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."}},"renderedAt":1782980147408}

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 adopti…