Learn-skills.dev shared-tooling-git-hooks
Husky v9 setup, lint-staged v16 patterns, commitlint with conventional commits, CI/production handling, monorepo setup, migration from v8
git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agents-inc/skills/shared-tooling-git-hooks" ~/.claude/skills/neversight-learn-skills-dev-shared-tooling-git-hooks && rm -rf "$T"
data/skills-md/agents-inc/skills/shared-tooling-git-hooks/SKILL.mdGit Hooks
Quick Guide: Husky v9 for git hooks with
(NOT"prepare": "husky"). lint-staged v16 for staged-only linting. commitlint for conventional commit messages. Pre-commit hooks should take < 10 seconds. Set"husky install"in CI/production.HUSKY=0
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use
in package.json - NOT the deprecated "prepare": "husky"
)"husky install"
(You MUST only lint staged files via lint-staged - NEVER lint the entire codebase in pre-commit)
(You MUST set
in CI/production environments to disable hooks)HUSKY=0
(You MUST use plain hook files in
directory - NO shebang lines or husky.sh sourcing in v9).husky/
(You MUST keep pre-commit hooks under 10 seconds - move slow tasks to pre-push or CI)
</critical_requirements>
Auto-detection: Husky, husky init, .husky/, pre-commit hook, lint-staged, commitlint, conventional commits, commit-msg hook, git hooks, prepare script
When to use:
- Setting up pre-commit hooks with Husky + lint-staged
- Configuring commit message validation with commitlint
- Migrating from Husky v8 to v9
- Configuring git hooks in monorepo setups
- Disabling hooks in CI/production environments
When NOT to use:
- Linter/formatter configuration itself (separate concern)
- CI/CD pipeline configuration (separate concern)
- Runtime application code (this is developer workflow tooling only)
Key patterns covered:
- Husky v9 setup and hook creation
- lint-staged v16 configuration patterns
- commitlint with conventional commits
- CI/production hook disabling
- Monorepo setup
- Migration from Husky v8 to v9
Detailed Resources:
- examples/core.md - Setup, lint-staged config, commitlint, CI handling, monorepo, migration
- reference.md - Decision frameworks, tool comparison, anti-patterns
<philosophy>
Philosophy
Git hooks are a developer workflow tool - they catch issues early while staying fast and non-blocking. The goal is fast feedback (< 10 seconds) on staged files only. Hooks are optional infrastructure; many projects work fine without them.
When to use git hooks:
- Team projects where code quality gates prevent CI failures
- Projects with established linting/formatting that should be enforced
- When you want fast feedback before code reaches CI
- Monorepos where running full lint is too slow
When NOT to use:
- Solo projects where you always remember to lint (overhead without benefit)
- Projects without established linting/formatting rules yet (set up linting first)
- When pre-commit hooks exceed 10 seconds (move to CI instead)
- CI-only projects where hooks add friction without value
<patterns>
Core Patterns
Pattern 1: Husky v9 Setup
Husky v9 uses plain shell scripts in
.husky/ directory. No shebang lines needed. The prepare script auto-installs hooks for all team members.
# Full setup in 4 commands bun add -D husky bunx husky init # Creates .husky/ and adds "prepare": "husky" to package.json bun add -D lint-staged echo "bunx lint-staged" > .husky/pre-commit
Key points:
(NOT"prepare": "husky"
- deprecated, will break in v10)"husky install"- Hook files are plain shell scripts (no shebang required in v9)
disables hooks (CI/production);HUSKY=0
enables debug modeHUSKY=2- v9.1.1+ allows running package commands directly without npx/bunx
See examples/core.md for full setup and package.json configuration.
Pattern 2: Pre-commit Hook with lint-staged
lint-staged v16 runs commands only on staged files. Uses
picomatch for glob matching (replaced micromatch).
// lint-staged.config.mjs export default { "*.{ts,tsx}": ["eslint --fix", "prettier --write"], "*.{css,scss}": ["prettier --write"], };
Type checking requires function syntax (runs on ALL files, not just staged):
// lint-staged.config.mjs — with type checking export default { "*.{ts,tsx}": ["eslint --fix", "prettier --write"], "*.{ts,tsx,js,jsx}": () => "tsc --noEmit", };
Why good: Only staged files, auto-fix reduces manual work, fast feedback
v16 breaking changes:
--shell flag removed (use shell scripts instead), requires Node.js 20.18+
See examples/core.md for multiple file type patterns and v16 migration details.
Pattern 3: Commitlint with Conventional Commits
commitlint v20+ validates commit messages. ESM-native - use
.mjs config extension.
bun add -D @commitlint/cli @commitlint/config-conventional
# .husky/commit-msg bunx commitlint --edit $1
// commitlint.config.mjs (MUST be .mjs for Node v24 compatibility) export default { extends: ["@commitlint/config-conventional"], };
Format:
type(scope): description - types: feat, fix, docs, refactor, test, chore, etc.
See examples/core.md for v20 changes and advanced configuration.
Pattern 4: CI/Production Environment Handling
Disable Husky where hooks should not run.
# CI pipelines HUSKY=0 npm install # GitHub Actions env: HUSKY: 0
Why: Prevents hook installation failures when
devDependencies not installed in production.
See examples/core.md for conditional prepare script alternatives.
Pattern 5: Monorepo Setup
For monorepos where package.json is not at the repository root.
// apps/frontend/package.json { "scripts": { "prepare": "cd ../.. && husky apps/frontend/.husky" } }
Key: Navigate to repo root, then pass the
.husky/ directory path to Husky.
Pattern 6: Migration from Husky v8 to v9
Step-by-step: update prepare script, remove shebangs/husky.sh sourcing from hook files, delete
.husky/.gitignore, remove pinst if used.
Important: Hooks with deprecated shebang
#!/usr/bin/env sh and husky.sh sourcing will fail in v10.0.0. Migrate now.
See examples/core.md for the full migration walkthrough.
</patterns><decision_framework>
Decision Framework
What to Run Pre-commit
What to run pre-commit? ├─ Fast (< 10 seconds)? │ ├─ Lint with auto-fix → YES (lint-staged) │ ├─ Format → YES (lint-staged) │ └─ Type check (--noEmit) → YES (lint-staged) └─ Slow (> 10 seconds)? ├─ Full test suite → NO (pre-push or CI) ├─ Full build → NO (CI) └─ E2E tests → NO (CI)
Which Hook for Which Task
Which Git hook? ├─ Before committing? → pre-commit (lint-staged) ├─ Validating commit message? → commit-msg (commitlint) ├─ Before pushing? → pre-push (type check, unit tests) ├─ Before merging? → pre-merge-commit (v9.1.5+) └─ After checkout/merge? → post-checkout / post-merge (install deps)
Pre-commit Timing Guidelines
| Task | Time | Pre-commit? |
|---|---|---|
| lint-staged (staged files only) | < 5s | Yes |
| Code formatting | < 2s | Yes |
| Type check (--noEmit) | < 10s | Yes |
| Full test suite | > 30s | No (CI) |
| E2E tests | > 60s | No (CI) |
| Full build | > 30s | No (CI) |
See reference.md for Husky vs alternatives comparison.
</decision_framework>
<red_flags>
RED FLAGS
High Priority Issues:
- Using deprecated
instead of"prepare": "husky install"
(will break in v10)"prepare": "husky" - Running full lint on entire codebase in pre-commit hook (too slow, defeats staged-only purpose)
- Using v4-style
config in package.json (not supported in v9)"husky": { "hooks": {} } - Using
environment variable (deprecated, useHUSKY_GIT_PARAMS
instead)$1
Medium Priority Issues:
- Pre-commit hooks taking > 10 seconds (encourages
abuse)--no-verify - Missing
in CI/production (hooks may fail when devDependencies not installed)HUSKY=0 - Using deprecated shebang
and husky.sh sourcing in hook files (will fail in v10)#!/usr/bin/env sh - Using commitlint config with
extension on Node v24 (use.js
instead).mjs
Gotchas & Edge Cases:
bypasses hooks entirely - emergency escape hatch, not for regular usegit commit --no-verify- lint-staged function syntax
runs on ALL files, not just staged() => "tsc --noEmit" - Hook file names must match Git's exact names (
,pre-commit
) - no extensions, case-sensitivecommit-msg
enables debug mode (replaces deprecatedHUSKY=2
)HUSKY_DEBUG=1- commitlint v20+ is ESM-native -
config extension avoids module loading issues.mjs - Windows users need to escape
in commit-msg hook$1 - In monorepos,
script must navigate to repo root before running huskyprepare
support will be removed in v10 - migrate to~/.huskyrc.config/husky/init.sh- lint-staged v16 uses
(notpicomatch
) - glob patterns may differ slightlymicromatch
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST use
in package.json - NOT the deprecated "prepare": "husky"
)"husky install"
(You MUST only lint staged files via lint-staged - NEVER lint the entire codebase in pre-commit)
(You MUST set
in CI/production environments to disable hooks)HUSKY=0
(You MUST use plain hook files in
directory - NO shebang lines or husky.sh sourcing in v9).husky/
(You MUST keep pre-commit hooks under 10 seconds - move slow tasks to pre-push or CI)
Failure to follow these rules will cause slow commits, broken CI builds, and deprecated hook patterns that will fail in Husky v10.
</critical_reminders>