Git Hooks Automation skill for Claude Code
Fast commit hooks backed by the same checks in CI. Claude chooses one hook framework for the repo (pre-commit, lefthook or husky with lint-staged), splits checks between commit, push and CI so commits stay fast, and pins every hook version. Fixes are re-staged without touching unstaged work, gitleaks blocks secrets before they reach history, hooks install themselves, and CI runs the same checks so --no-verify bypasses nothing.
- name
git-hooks-automation - size168 lines · ≈1,850 tokens
- files2
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Sets up git hooks that developers keep enabled — pre-commit framework, lefthook, or husky with lint-staged — running formatters and linters on staged files only, secret scanning with gitleaks, conventional commit message checks, heavier checks on pre-push, automatic installation for every clone, and the same checks enforced in CI because hooks can be skipped. Use when adding or fixing pre-commit hooks, choosing between pre-commit, lefthook, and husky, speeding up slow commit hooks, blocking secrets from being committed, or enforcing commit message conventions. Try asking
- Add pre-commit hooks for ruff, prettier and secret scanning to this repo
- Our husky hooks take 40 seconds and everyone uses --no-verify, make them fast
- Should we use lefthook or pre-commit for our Go and TypeScript monorepo? Set it up
Use it when
- Adding linting, formatting or secret scanning on commit
- Commit hooks are slow or get skipped
- Choosing between pre-commit, lefthook and husky
- Enforcing commit message conventions
Skip it when
- You want hooks that run around Claude Code tool calls — use hooks-automation
- You are building the CI workflow itself — use github-actions
How to check the result
A fresh clone installs hooks automatically, a staged lint error and a fake secret are both blocked, and a PR made with --no-verify fails the same checks in CI.
The common mistake it prevents
Treating local hooks as enforcement, so anyone using --no-verify or a GUI client that skips hooks merges unchecked code.
Done well means: hooks install themselves on clone or dependency install, a typical
commit’s hooks finish in a few seconds, they only touch staged files, auto-fixes are
re-staged correctly, secrets are blocked before they enter history, and CI runs the same
checks so --no-verify cannot sneak anything past review.
Hard rules
- Hooks are a convenience, not enforcement. Anything that must hold (lint, format, tests, secret scan) also runs in CI on every pull request.
- pre-commit must be fast: target under ~5 seconds for a normal commit. Run only on staged files; move type checks, full test suites, and builds to pre-push or CI.
- Pin hook versions (tags or exact package versions). Never
rev: main. - Never auto-commit or push from a hook, and never rewrite unstaged work. Use tools that
stash unstaged changes (lint-staged, pre-commit framework) or that re-stage only fixed
files (lefthook
stage_fixed). - If a secret is detected in an existing commit, the fix is rotation first. History rewriting (force push) needs explicit user confirmation.
Pick the framework
| Repo | Default | Why |
|---|---|---|
| Python or polyglot; want isolated, versioned tool environments | pre-commit (or prek, a faster drop-in that reads the same config) | Huge hook ecosystem; each hook gets its own env; pre-commit/action for CI |
| Polyglot or monorepo, speed matters, no Python dependency wanted | lefthook | Single Go binary, parallel commands, glob filters, {staged_files} |
| JavaScript/TypeScript repo | husky + lint-staged | Installs via npm install (prepare script), familiar to JS devs |
Existing hooks in .git/hooks scripts | Migrate to one of the above | .git/hooks is not versioned or shared |
Use one framework per repo. Running husky and pre-commit together causes double runs and install conflicts.
Workflow
- [ ] 1. Inventory: existing linters/formatters, CI checks, current .git/hooks, core.hooksPath
- [ ] 2. Choose framework; decide which checks run at commit vs. push vs. CI
- [ ] 3. Write config (pinned versions); staged-files-only commands
- [ ] 4. Add secret scanning and commit-msg check if the team uses conventions
- [ ] 5. Make installation automatic for every contributor
- [ ] 6. Run on all files once; fix or baseline existing violations in a separate commit
- [ ] 7. Mirror the checks in CI
- [ ] 8. Measure hook time; move slow checks later
2. What runs where
| Check | pre-commit | commit-msg | pre-push | CI |
|---|---|---|---|---|
| Formatter (prettier, ruff format, gofmt) with auto-fix | yes | verify only | ||
| Linter on changed files (eslint, ruff) | yes | yes, all files | ||
| Secret scan of staged diff (gitleaks) | yes | yes, full range | ||
| Large file / merge-conflict marker / private key checks | yes | yes | ||
| Commit message convention (commitlint, conventional-pre-commit) | yes | PR title or commits | ||
| Type check (tsc, mypy) | yes | yes | ||
| Unit tests | optional, fast subset | yes, full | ||
| Build, e2e | yes |
3. Configs
Write the config for the chosen framework. Full, commented examples for
.pre-commit-config.yaml, lefthook.yml, husky + lint-staged, commitlint, and the CI
job are in references/configs.md; read it when writing the file.
Minimal shapes:
# .pre-commit-config.yaml
default_install_hook_types: [pre-commit, commit-msg]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: vX.Y.Z # pin; `pre-commit autoupdate` bumps
hooks: [{ id: check-merge-conflict }, { id: check-added-large-files }, { id: end-of-file-fixer }]
- repo: https://github.com/gitleaks/gitleaks
rev: vX.Y.Z
hooks: [{ id: gitleaks }]
# lefthook.yml
pre-commit:
parallel: true
commands:
lint:
glob: "*.{ts,tsx,js}"
run: npx eslint --fix {staged_files}
stage_fixed: true
5. Automatic installation
| Framework | Make it automatic |
|---|---|
| pre-commit | Document pre-commit install in setup; add it to the repo’s setup task (see task-runners); pre-commit install --install-hooks pre-builds envs |
| lefthook | npm i -D lefthook runs lefthook install on postinstall; otherwise add lefthook install to the setup task |
| husky | "prepare": "husky" in package.json runs on npm install |
Check for a custom core.hooksPath (git config --get core.hooksPath); a stale value
makes installed hooks silently never run.
6. First run on an existing codebase
pre-commit run --all-files # or: lefthook run pre-commit --all-files / npx lint-staged --diff=...
Commit mass reformatting separately with a clear message, and add that commit’s SHA to
.git-blame-ignore-revs (git config blame.ignoreRevsFile .git-blame-ignore-revs)
so blame stays useful. For pre-existing secrets findings, rotate them, then use a
gitleaks baseline or .gitleaksignore entries (by fingerprint) only for confirmed false
positives.
7. CI mirror
Run the framework in CI over the whole PR, not only staged files: pre-commit/action,
lefthook run pre-commit --all-files, or the underlying lint/format commands in check
mode (prettier --check, ruff format --check). Without this, anyone with
--no-verify, a GUI client that skips hooks, or a fresh clone without installation
bypasses everything.
8. Speed
git add <a typical change> && time git commit -m "chore: hook timing" # then reset if it was only a test
pre-commit run --all-files --verbose # shows per-hook duration
LEFTHOOK_VERBOSE=1 lefthook run pre-commit
- Filter by file type (
types:/files:in pre-commit,glob:in lefthook, globs in lint-staged) so unrelated hooks do not start. - Run independent commands in parallel (lefthook
parallel: true; lint-staged runs globs concurrently). - Use
language: system/ project-installed tools for heavy toolchains already present, instead of building isolated environments. - Anything consistently over a few seconds moves to pre-push or CI.
Verification
- Fresh clone + setup command -> hooks present (
ls .git/hooks/orlefthook versionandgit config core.hooksPath). - Stage a file with a lint error: commit is blocked with a readable message.
- Stage a formatting issue: it is fixed and re-staged; unstaged edits in the same file survive untouched.
- Stage a fake secret (e.g., an AWS-style test key in a scratch file): commit is blocked. Remove the file afterwards.
- Bad commit message is rejected when a convention is enabled.
- Open a PR with
--no-verifycommits: CI fails on the same checks.
Gotchas
- Partially staged files: tools that run on the working tree instead of the staged version can pass on unstaged fixes or clobber them; rely on the framework’s stash handling.
- GUI git clients and some IDEs run hooks with a minimal PATH; tools installed via
nvm/pyenv shims may be “not found”. Use project-local binaries (
npx,uv run) or absolute paths. - Windows: shell hooks need Git Bash; avoid bash-only syntax in husky hook files if Windows contributors use other shells.
- Hooks that take 30 seconds get disabled with
--no-verifyhabitually; speed is a feature of the security control. - Rebase and merge commits also trigger hooks; make them idempotent and quiet when there is nothing to check.
Bundled files
Loaded only when a step in the skill needs them. Review them here before installing.
references/configs.md
# Git hook configurations
Read this when writing the actual config file. Versions below are placeholders on
purpose: run `pre-commit autoupdate` (or check each project's releases) and pin the
current tags; never leave `rev: main` or `latest`.
## pre-commit (multi-language, Python-installed)
```yaml
# .pre-commit-config.yaml
default_install_hook_types: [pre-commit, commit-msg, pre-push]
default_stages: [pre-commit]
fail_fast: false
exclude: '^(vendor/|third_party/|.*\.min\.js$)'
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: vX.Y.Z
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-merge-conflict
- id: check-added-large-files
args: [--maxkb=500]
- id: detect-private-key
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: vX.Y.Z
hooks:
- id: ruff-check
args: [--fix]
- id: ruff-format
- repo: https://github.com/gitleaks/gitleaks
rev: vX.Y.Z
hooks:
- id: gitleaks
- repo: https://github.com/compilerla/conventional-pre-commit
rev: vX.Y.Z
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
- repo: local
hooks:
- id: mypy
name: mypy
entry: uv run mypy
language: system
types: [python]
pass_filenames: false
stages: [pre-push] # slower check: run on push, not every commit
```
Notes:
- Older ruff-pre-commit releases name the lint hook `ruff` instead of `ruff-check`;
match the hook id to the pinned rev.
- `language: system` uses tools already installed (fast, but not isolated); use it for
project-managed tools run via `uv run`, `npx`, `go run`.
- Commands: `pre-commit install` (honors `default_install_hook_types`),
`pre-commit run --all-files`, `pre-commit run <id> --files a.py`,
`pre-commit autoupdate`, `SKIP=mypy git commit ...`, `pre-commit gc`.
- `prek` is a compatible, faster reimplementation that reads the same config file; it
can replace the `pre-commit` binary if install speed matters.
## lefthook (single binary, any language, parallel)
```yaml
# lefthook.yml
pre-commit:
parallel: true
commands:
format-js:
glob: "*.{js,ts,tsx,json,css,md}"
run: npx prettier --write {staged_files}
stage_fixed: true
lint-js:
glob: "*.{js,ts,tsx}"
run: npx eslint --fix {staged_files}
stage_fixed: true
go-fmt:
glob: "*.go"
run: gofmt -l -w {staged_files}
stage_fixed: true
secrets:
run: gitleaks git --pre-commit --staged --redact --no-banner
commit-msg:
commands:
conventional:
run: npx --no -- commitlint --edit {1}
pre-push:
parallel: true
commands:
typecheck:
run: npx tsc --noEmit
test:
run: npm test
```
Notes:
- Install: `npm i -D lefthook` (adds a postinstall that runs `lefthook install`), or
`brew install lefthook && lefthook install`.
- `{staged_files}`, `{push_files}`, `{all_files}`, `{1}` (first hook argument, the
commit message file for commit-msg).
- `stage_fixed: true` re-adds files the command modified.
- Local overrides (not committed): `lefthook-local.yml`. Skip: `LEFTHOOK=0 git commit`
or `LEFTHOOK_EXCLUDE=test git push`.
- Run manually: `lefthook run pre-commit --all-files`.
- gitleaks flags changed in v8.19 (`git`/`dir` subcommands replaced `detect`/`protect`);
confirm with `gitleaks git --help` for your installed version.
## husky + lint-staged (JavaScript repos)
```bash
npm i -D husky lint-staged
npx husky init # creates .husky/pre-commit and adds "prepare": "husky" to package.json
```
```sh
# .husky/pre-commit (plain shell; husky v9 needs no shebang boilerplate)
npx lint-staged
```
```sh
# .husky/commit-msg
npx --no -- commitlint --edit "$1"
```
```json
// package.json (excerpt)
{
"scripts": { "prepare": "husky" },
"lint-staged": {
"*.{ts,tsx,js}": ["eslint --fix --max-warnings=0", "prettier --write"],
"*.{json,md,css,yml}": "prettier --write"
}
}
```
Notes:
- lint-staged stashes unstaged changes, runs tasks on staged content, re-adds fixed
files, and restores on failure.
- Disable in CI or for a command: `HUSKY=0`. In CI, `prepare` runs on `npm ci`;
husky skips installing when there is no `.git` directory.
- Do not run `tsc` in lint-staged on individual files (it ignores tsconfig); run the
project typecheck in pre-push or CI.
## commitlint
```js
// commitlint.config.js
export default { extends: ['@commitlint/config-conventional'] };
```
## CI job (run the same checks; hooks are bypassable)
```yaml
# GitHub Actions
jobs:
hooks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-python@v5
with: { python-version: '3.12' }
- uses: pre-commit/[email protected] # runs pre-commit run --all-files with caching
```
For lefthook: `npx lefthook run pre-commit --all-files` (and `pre-push` checks as
separate CI steps). For husky repos: run `npx lint-staged --diff="origin/main...HEAD"`
or simply the full lint/format/test commands.
## Secret scanning in history (one-off audit)
```bash
gitleaks git --redact -v . # whole history of the current repo
trufflehog git file://. --only-verified
```
If a real secret is found: rotate it first (revocation is what protects you), then
decide with the user whether to rewrite history (see git-history-surgery).
About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.