Coding 101

Automated Git Hygiene: Production Workflows with Husky v9, lint-staged, and Commitlint

DD
Ankur Ishwar
6 min read Updated Sep 6, 2026
Automated Git Hooks with Husky v9 and lint-staged

The High Cost of Broken Commits

It is Friday afternoon. An engineer pushes a three-line patch to main. The commit message reads "quick fix". Ten minutes later, your staging continuous integration (CI) runner fails because of a missing semicolon in a TypeScript declaration. The team wastes thirty minutes diagnosing what a simple pre-commit linter would have caught in four hundred milliseconds.

Relying on human discipline to format code, enforce commit standards, and verify unit tests before pushing is a guaranteed recipe for broken builds. Production engineering teams automate these checks locally using Git hooks.

While Git has always supported client hooks inside the .git/hooks/ directory, these files are not tracked by version control. Husky bridges this gap by mapping repository Git hooks to tracked shell scripts. Here is how to configure a bulletproof workflow using Husky v9, lint-staged, and Commitlint.

1. The Modern Husky v9 Architecture

Legacy versions of Husky (v4) stored configurations inside package.json under a "husky" key or in .huskyrc files. That pattern was deprecated years ago. In Husky v9, hooks are standard, lightweight shell scripts placed in an explicit .husky/ folder at the root of your project.

To initialize Husky v9 cleanly in a modern Node.js or TypeScript project:

# 1. Install Husky as a development dependency
npm install --save-dev husky

# 2. Initialize Husky (creates .husky/ directory and adds 'prepare' script to package.json)
npx husky init

# 3. Verify that package.json includes the prepare script:
# "scripts": {
#   "prepare": "husky"
# }

The "prepare": "husky" lifecycle script ensures that whenever another teammate or CI runner runs npm install, Husky automatically activates Git hooks locally.

2. Lightning-Fast Checks with lint-staged

Running your entire test suite or linting 4,000 files before every commit takes thirty seconds. Developers will inevitably bypass the hook using git commit --no-verify. To make pre-commit hooks frictionless, run linters and formatters only on the files currently staged for commit.

Install lint-staged and configure your tools:

npm install --save-dev lint-staged prettier eslint

Create a dedicated configuration file in your project root:

// lint-staged.config.js
export default {
  // Format markdown, JSON, and CSS files
  '*.{json,md,css,scss}': ['prettier --write'],

  // Lint and format TypeScript and JavaScript files
  '*.{js,jsx,ts,tsx}': [
    'prettier --write',
    'eslint --fix --max-warnings=0',
  ],
};

Next, configure your .husky/pre-commit script to invoke lint-staged:

# .husky/pre-commit
npx lint-staged

Now, when you run git commit, Prettier formats only the files you touched, ESLint validates their syntax, and changes are automatically restaged before the commit snapshot is created. Total execution time: less than 1.5 seconds.

3. Enforcing Conventional Commits with Commitlint

Consistent commit histories make automated semantic releases, changelog generation, and git bisect debugging possible. To prevent commit messages like "changes" or "fix error", use Commitlint to validate the commit message string during the commit-msg hook phase.

Install Commitlint and the conventional ruleset:

npm install --save-dev @commitlint/cli @commitlint/config-conventional

Create your Commitlint configuration file:

// commitlint.config.js
export default {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'build',
        'chore',
        'ci',
        'docs',
        'feat',
        'fix',
        'perf',
        'refactor',
        'revert',
        'style',
        'test',
      ],
    ],
    'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']],
    'subject-empty': [2, 'never'],
    'subject-full-stop': [2, 'never', '.'],
  },
};

Now create the .husky/commit-msg executable hook:

# .husky/commit-msg
npx --no -- commitlint --edit "$1"

If an engineer attempts to commit with an invalid message format, Commitlint intercepts the command and prints a clear explanation:

$ git commit -m "Fixed login bug"
⧗   input: Fixed login bug
✖   type must be lower-case [type-case]
✖   type must be one of [feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert] [type-enum]
✖   found 2 errors, 0 warnings

Commit aborted. Use format: fix(auth): prevent session timeout on redirect

4. Guarding Remote Branches with Pre-Push Hooks

While pre-commit is ideal for fast formatting and linting, full type-checking and unit tests are best suited for the pre-push hook. This prevents pushing broken code to remote GitHub branches while keeping local commits fast.

Create the .husky/pre-push script:

# .husky/pre-push
echo "Running typechecks and unit tests before push..."

# Run TypeScript compiler in noEmit mode
npm run typecheck || {
  echo "TypeScript compilation failed! Push aborted."
  exit 1
}

# Run unit tests
npm test -- --run || {
  echo "Unit test suite failed! Push aborted."
  exit 1
}

Summary: The Three Gates of Defense

  • Gate 1 (Pre-Commit): lint-staged formats and lints staged files in under two seconds.
  • Gate 2 (Commit-Msg): Commitlint guarantees structured, semantic commit messages.
  • Gate 3 (Pre-Push): Type checking and test suites run before any branch updates reach the remote server.

Implementing these three automated gates guarantees that bad commits never contaminate your repository, freeing your code reviews to focus on architecture and logic rather than formatting arguments.

Found this useful?
View all articles
Free Technical Interview Prep

Practicing for Engineering Interviews?

Skip the expensive coaching bootcamps and dry LeetCode memorization. Practice real production scenarios with instant turn-by-turn AI feedback on Frontend, Backend, System Design, and DSA.

Free Utilities

Recommended Developer Tools for this Topic

Explore all 25+ tools→

Keep Reading

Related Articles

Learn with Dropout Developer

Build real software with AI

Step-by-step learning paths, vibe coding tutorials, and certified developer programs designed for the modern engineer.