Git Hooks
Git hooks automation with Husky and lint-staged for pre-commit quality gates
You are an expert in Git hooks automation using Husky and lint-staged for pre-commit quality gates. ## Key Points - `pre-commit` — runs before a commit is created; used for linting, formatting, type checking - `commit-msg` — runs after the message is written; used to enforce commit message format - `pre-push` — runs before pushing; used for test suites or build verification - `prepare-commit-msg` — runs before the editor opens; used to prepend templates or ticket numbers - Use lint-staged to scope checks to staged files only; running linters on the entire codebase in a pre-commit hook is too slow - Keep pre-commit hooks under 10 seconds; move heavier checks (full test suite, builds) to pre-push or CI - Commit the `.husky/` directory so every team member gets the same hooks automatically after `npm install` - Forgetting to run `npx husky init` or `npm install` after cloning, which means hooks are never registered - Using `--no-verify` habitually to skip slow hooks instead of fixing the hooks to be faster, which defeats the purpose of having them ## Quick Example ```bash # .husky/commit-msg npx --no -- commitlint --edit "$1" ``` ```bash # .husky/pre-push npm test ```
skilldb get git-workflow-skills/Git HooksFull skill: 116 linesGit Hooks — Git Workflows
You are an expert in Git hooks automation using Husky and lint-staged for pre-commit quality gates.
Overview
Git hooks are scripts that run automatically at specific points in the Git lifecycle (pre-commit, commit-msg, pre-push, etc.). Husky makes hooks easy to manage in a team setting by storing them in the repository, and lint-staged runs linters only on staged files to keep pre-commit checks fast.
Core Concepts
Common hook stages:
pre-commit— runs before a commit is created; used for linting, formatting, type checkingcommit-msg— runs after the message is written; used to enforce commit message formatpre-push— runs before pushing; used for test suites or build verificationprepare-commit-msg— runs before the editor opens; used to prepend templates or ticket numbers
How Husky works:
Husky installs a Git hook runner into .husky/ and configures Git's core.hooksPath to point there. Hook scripts are plain shell files committed to the repository.
Implementation Patterns
Husky + lint-staged setup:
# Install
npm install --save-dev husky lint-staged
# Initialize Husky (creates .husky/ directory)
npx husky init
# Create pre-commit hook
echo "npx lint-staged" > .husky/pre-commit
lint-staged configuration (package.json):
{
"lint-staged": {
"*.{js,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss}": [
"stylelint --fix",
"prettier --write"
],
"*.{json,md,yml}": [
"prettier --write"
]
}
}
Commit message validation hook:
# .husky/commit-msg
npx --no -- commitlint --edit "$1"
Pre-push hook for tests:
# .husky/pre-push
npm test
Custom hook — prepend JIRA ticket from branch name:
# .husky/prepare-commit-msg
BRANCH=$(git symbolic-ref --short HEAD)
TICKET=$(echo "$BRANCH" | grep -oE '[A-Z]+-[0-9]+')
if [ -n "$TICKET" ] && ! grep -q "$TICKET" "$1"; then
sed -i.bak "1s/^/[$TICKET] /" "$1"
fi
Core Philosophy
Git hooks shift quality checks left — from CI (where failures are slow and disruptive) to the developer's machine (where failures are instant and private). A developer who discovers a linting error 30 seconds after committing can fix it immediately without context-switching. The same developer discovering the error 10 minutes later in CI has already moved on to the next task, must switch back, push a fix, and wait for CI again. The faster the feedback loop, the cheaper the fix. Pre-commit hooks make the feedback loop nearly instantaneous.
The critical design constraint for pre-commit hooks is speed. A hook that takes 30 seconds to run will be bypassed with --no-verify within a week. A hook that takes 2 seconds becomes invisible — part of the natural rhythm of committing. This is why lint-staged exists: instead of running ESLint on every file in the project, it runs only on the files staged for commit. The scope is minimal, the runtime is fast, and the feedback is immediate. Move heavyweight checks (full test suites, type-checking the entire project, builds) to pre-push or CI, where the developer expects a longer wait.
Husky solves the distribution problem that makes native Git hooks impractical for teams. Git hooks live in .git/hooks/, which is not tracked by version control. Without Husky, every developer must manually install hooks after cloning, and there is no way to update hooks across the team. Husky stores hook scripts in .husky/ (tracked in the repository) and configures Git's core.hooksPath to point there during npm install. This means hooks are automatically available to every team member and updated with every pull. The hooks become part of the project, not a local configuration that varies between machines.
Anti-Patterns
-
Full-project linting in pre-commit. Running ESLint or Prettier on every file in the repository during pre-commit takes too long and trains developers to skip hooks with
--no-verify. Use lint-staged to scope checks to only the files being committed. -
Habitual
--no-verify. Bypassing hooks regularly because they are too slow or too noisy defeats the purpose of having them. If developers routinely skip hooks, the hooks need to be faster or less aggressive, not ignored. Fix the hooks rather than normalizing the bypass. -
Heavy test suites in pre-commit. Running the full test suite before every commit makes committing painful and discourages small, frequent commits. Reserve pre-commit for fast checks (linting, formatting, type-checking staged files). Move test suites to pre-push or CI.
-
Not committing the
.husky/directory. Keeping Husky hooks out of version control means every developer must set up hooks manually, and there is no guarantee the team is running the same checks. Commit the.husky/directory so hooks are part of the project. -
Hooks that modify files without staging them. A pre-commit hook that auto-formats files but does not
git addthe formatted files creates a commit where the committed content does not match the file on disk. lint-staged handles this correctly by re-staging files after formatting, but custom hooks must do it explicitly.
Best Practices
- Use lint-staged to scope checks to staged files only; running linters on the entire codebase in a pre-commit hook is too slow
- Keep pre-commit hooks under 10 seconds; move heavier checks (full test suite, builds) to pre-push or CI
- Commit the
.husky/directory so every team member gets the same hooks automatically afternpm install
Common Pitfalls
- Forgetting to run
npx husky initornpm installafter cloning, which means hooks are never registered - Using
--no-verifyhabitually to skip slow hooks instead of fixing the hooks to be faster, which defeats the purpose of having them
Install this skill directly: skilldb add git-workflow-skills
Related Skills
Code Review
Code review best practices for constructive, efficient pull request reviews
Conventional Commits
Conventional Commits specification for structured, machine-readable commit messages
Git Bisect Debug
Debugging with git bisect and advanced git log techniques to pinpoint regressions
Gitflow
Gitflow branching model for structured release-oriented development workflows
Monorepo Management
Monorepo strategies with Nx and Turborepo for scalable multi-project repositories
Release Management
Release management and tagging strategies for predictable, automated software releases