Skip to main content
Technology & EngineeringGit Workflow116 lines

Git Hooks

Git hooks automation with Husky and lint-staged for pre-commit quality gates

Quick Summary27 lines
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 lines
Paste into your CLAUDE.md or agent config

Git 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 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

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 add the 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 after npm install

Common Pitfalls

  • 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

Install this skill directly: skilldb add git-workflow-skills

Get CLI access →