Set Up Husky + lint-staged with Claude Code: A Practical pre-commit Guide
Use Claude Code, Husky, and lint-staged to run ESLint and Prettier before commits without slowing the team down.
Claude Code can touch a surprising number of files in a single session. That is useful when you are refactoring, adding tests, or cleaning up a feature, but it also means tiny mistakes are easier to miss. A reviewer should spend attention on behavior and product intent, not on unused imports, inconsistent formatting, or a Markdown table that changed shape.
This is where a small pre-commit system pays for itself. A Git hook is a script Git runs at a specific point in a workflow. Husky makes those hooks easy to keep inside the repository, and lint-staged runs commands only on files that are already staged with git add. Claude Code hooks are a different feature: they run during Claude Code’s own lifecycle. They are useful, but they do not replace Git hooks because they only protect Claude Code sessions. A pre-commit hook protects every commit, no matter who created the change.
The goal is not to build an elaborate local CI server. The goal is to make the cheap checks automatic and unavoidable, while leaving expensive checks to pre-push and CI.
The Shape of the Workflow
The most reliable split I use is simple: pre-commit handles fast, file-local checks; pre-push handles medium-cost project checks; CI remains the source of truth. That keeps local commits fast enough that developers do not reach for --no-verify.
flowchart LR
A["Claude Code edits files"] --> B["Developer stages chosen files"]
B --> C["Husky pre-commit"]
C --> D["lint-staged"]
D --> E["ESLint / Prettier"]
E --> F["commit"]
C --> G["typecheck, tests, build live in pre-push or CI"]
The current official references behind this setup are Husky Get started, the lint-staged README, the Git hooks manual, and the Claude Code hooks reference. The important practical detail is that Husky’s recommended setup uses npx husky init, and lint-staged still needs a pre-commit hook plus a configuration that maps file globs to commands.
Minimum Working Setup
If ESLint and Prettier are not settled yet, first harden those foundations with the Claude Code ESLint configuration guide and the Prettier configuration guide. Husky is most useful when the underlying commands are already boring and predictable.
When asking Claude Code to implement this, be specific about scope and cost:
Add Husky and lint-staged to this Node.js/TypeScript repository.
The pre-commit hook should only run on staged JS, TS, JSON, Markdown, and CSS files.
Run ESLint auto-fix and Prettier in pre-commit.
Keep typecheck, tests, and build out of pre-commit; put them in pre-push or CI.
After editing, list the manual verification commands.
The manual install path is short:
npm install --save-dev husky lint-staged eslint prettier
npx husky init
npx husky init creates .husky/pre-commit and updates the prepare script in package.json. Replace the generated hook body with this:
#!/usr/bin/env sh
npx lint-staged
Then add a small lint-staged configuration. If your package.json already has scripts, merge these keys instead of replacing the whole file.
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"prepare": "husky"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix --max-warnings=0",
"prettier --write"
],
"*.{json,md,mdx,yml,yaml,css,scss}": [
"prettier --write"
]
}
}
Test it with a deliberately messy staged file:
git add src/example.ts
npx lint-staged --debug
git commit -m "chore: verify pre-commit checks"
The --debug output is worth knowing. It shows which configuration file was loaded, which files matched each glob, and which commands ran. When Claude Code is debugging a failing hook, paste that output into the prompt instead of asking it to guess.
A Maintainable lint-staged Config
For a growing repository, I prefer a separate lint-staged.config.mjs. It keeps package.json readable and lets you use small helper functions. This example quotes filenames before passing them to the shell, which avoids common failures with paths containing spaces.
// lint-staged.config.mjs
const shellQuote = (file) => `"${file.replaceAll('"', '\\"')}"`;
const joinFiles = (files) => files.map(shellQuote).join(" ");
export default {
"*.{js,jsx,ts,tsx}": (files) => [
`eslint --fix --max-warnings=0 ${joinFiles(files)}`,
`prettier --write ${joinFiles(files)}`,
],
"*.{json,md,mdx,yml,yaml,css,scss}": (files) =>
`prettier --write ${joinFiles(files)}`,
};
Once this file exists, remove the lint-staged key from package.json. Two sources of truth are a maintenance trap. They also make AI-assisted edits less predictable, because the assistant may update one configuration and forget the other.
Three Practical Use Cases
The first use case is a TypeScript product app. Claude Code often edits components, tests, and utility types in the same pass. Running ESLint auto-fix and Prettier on staged files removes low-value review noise. Full type checking is better in pre-push or CI because TypeScript needs project context, and a full tsc --noEmit can be too slow for every small commit.
The second use case is a content-heavy Astro or Next.js site. MDX, JSON, CSS, and TypeScript frequently change together. lint-staged keeps prose diffs clean by formatting only the files that are about to be committed. That matters when the reviewer is checking the article content rather than fighting whitespace changes.
The third use case is a monorepo. Running every package test in pre-commit is usually a mistake. Start with staged-file formatting and linting, then let the task runner or CI decide which package tests are relevant. In prompts to Claude Code, I describe the policy as: pre-commit is fast, pre-push is medium, CI is complete.
commit-msg and pre-push
Commit message validation belongs in a separate commit-msg hook. If your team uses Conventional Commits, commitlint is the usual choice.
npm install --save-dev @commitlint/cli @commitlint/config-conventional
// commitlint.config.mjs
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"subject-max-length": [2, "always", 72],
},
};
#!/usr/bin/env sh
npx --no -- commitlint --edit "$1"
Put the shell snippet in .husky/commit-msg. It keeps message policy separate from code quality policy.
For heavier checks, use .husky/pre-push:
#!/usr/bin/env sh
npm run validate
A matching validate script keeps local and CI behavior aligned:
{
"scripts": {
"typecheck": "tsc --noEmit",
"test:ci": "vitest run --coverage",
"build": "vite build",
"validate": "npm run typecheck && npm run lint && npm run format:check && npm run test:ci && npm run build"
}
}
This split also helps Claude Code. When the assistant sees a failing pre-commit hook, it can focus on staged-file linting. When validate fails, it knows the failure is project-wide.
Pitfalls That Actually Happen
The most common failure is making pre-commit too ambitious. A hook that takes two minutes will be bypassed. A hook that takes five seconds becomes muscle memory. If people keep using git commit --no-verify, treat that as product feedback about your workflow.
Partial staging is the second trap. lint-staged operates on staged files, but the same file may also contain unstaged local edits. When debugging confusing results, inspect git status --short, git diff --staged, and npx lint-staged --debug together.
Windows line endings can also break hooks. Husky hook files are shell scripts, so keeping them as LF is the least surprising option.
* text=auto eol=lf
*.cmd text eol=crlf
*.bat text eol=crlf
Another trap is hiding real failures. Do not teach Claude Code to add || true to hooks. That converts a quality gate into theater. Ask it to print shorter errors, move heavy commands to pre-push, or document the fix command instead.
How Claude Code Fits In
Claude Code is best used to maintain the rules and explain failures, not as the only enforcement layer. I usually ask it to add three things with the hook: a short README note, a validate script that matches CI, and a troubleshooting section with npx lint-staged --debug.
Claude Code hooks can still help. For example, a Claude Code PostToolUse or Stop hook can remind the agent to run lint after edits, and a PreToolUse hook can block risky shell commands. But those hooks run inside Claude Code’s lifecycle. The final guard should remain Git hooks plus CI, because those run even when a human or another tool creates the commit.
What Happened When I Tried It
I tested this structure in a small TypeScript/Vite project with messy formatting, an unused import, and an MDX spacing issue. The pre-commit path stayed fast because it only processed staged files. The --debug output made it obvious which files matched each glob. Type errors were intentionally left for npm run validate in pre-push, which made day-to-day commits smoother without weakening the final check.
Summary
Husky and lint-staged are a practical safety rail for AI-assisted coding. Keep pre-commit small, move expensive checks to pre-push or CI, and tell Claude Code that this split is part of the design. Then the hook becomes a shared team habit instead of a slow local obstacle.
For the next step, tighten the underlying rules in the ESLint guide and standardize formatting with the Prettier guide.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Permission Safety Ladder: Expand Access Without Losing Control
A beginner-friendly ladder for moving Claude Code from read-only to limited edits, proof commands, and deploy checks.
Claude Code Small PR Proof Pack: Make Tiny Changes Reviewable
A practical proof pack for Claude Code PRs: diff, checks, public URL, CTA path, and rollback note.
Claude Code Review Gate Before Commit: Diff, Tests, Public URL, and CTA Checks
A commit-time review gate for Claude Code work: diff scope, build, public URL, revenue CTA links, missing tests, and unrelated files.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.