Husky Pre-Commit Hook Not Running: How to Fix

September 21, 2026 · 1 views
Husky Pre-Commit Hook Not Running: How to Fix

You commit code with a lint error, and nothing happens. No ESLint output, no Prettier run, no failed commit. If your Husky pre-commit hook is not running, the cause is almost always one of a short list of setup problems: the prepare script never ran, Git is not pointing at the hooks folder, the hook file has the wrong name or syntax, or you are still using pre-v9 Husky patterns. This guide walks through each cause in the order you should check it, with the exact commands to confirm and fix it.

How Husky Actually Works

Husky does not replace Git hooks. It configures Git to look for them in a different place. When you run husky (usually from an npm prepare script), it sets Git's core.hooksPath to .husky/_, and that folder contains small stubs that call the hook files you commit to .husky/.

That means a working setup has three moving parts:

  1. husky installed as a dev dependency.
  2. A prepare script in package.json that runs husky after npm install.
  3. A hook file such as .husky/pre-commit containing the command to run.

If any of the three is missing, or if the prepare step never executes on a given machine, Git falls back to its default .git/hooks folder and your hook silently does nothing. Silent is the key word here: Git will not warn you that a hook is not configured.

Step 1: Check That core.hooksPath Is Set

Start with the fastest diagnostic. From the repository root, run:

git config core.hooksPath

With Husky v9 you should see .husky/_. If the output is empty, Husky never installed itself in this clone, and the cause is one of the next two sections. If it prints a different path, something else (another hooks tool, a global Git config, or an old Husky version) is overriding it.

You can also check whether a global setting is interfering:

git config --show-origin --get-all core.hooksPath

Step 2: Make Sure the prepare Script Runs

Husky relies on npm's prepare lifecycle script. The standard v9 setup is:

npm install --save-dev husky lint-staged
npx husky init

npx husky init adds this to package.json and creates a sample .husky/pre-commit file:

{
  "scripts": {
    "prepare": "husky"
  }
}

The prepare script does not run in several common situations, and this is the number one reason a hook works on your machine but not on a teammate's:

  • Dependencies were installed with npm ci --ignore-scripts or a global ignore-scripts=true npm setting.
  • The project was cloned but npm install was never run.
  • A CI or Docker build installs only production dependencies, so husky is missing and the prepare script fails with husky: not found.

For the CI and Docker case, make the script tolerant so the install does not break when Husky is absent:

{
  "scripts": {
    "prepare": "husky || true"
  }
}

To repair a local clone immediately, run npm run prepare and check git config core.hooksPath again.

Step 3: Wire lint-staged Into the Hook

A hook file that only contains a placeholder such as npm test will not lint anything. Replace the contents of .husky/pre-commit with:

npx lint-staged

Then configure lint-staged in package.json:

{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{json,md,css}": "prettier --write"
  }
}

lint-staged passes the list of staged files to each command, so it only checks what you are about to commit. One caveat: commands that must analyze the whole project, such as tsc --noEmit, break when they receive file names. Use a function in lint-staged.config.js to run them without arguments:

export default {
  '*.{ts,tsx}': ['eslint --fix', () => 'tsc --noEmit'],
};

Step 4: Fix Wrong File Names and Old Husky Syntax

The hook file must be named exactly after the Git hook, with no extension: .husky/pre-commit, not .husky/precommit, .husky/pre-commit.sh, or .husky/pre-commit.txt.

Older tutorials also cause trouble. Husky v4 stored hooks in package.json, and v8 and earlier required boilerplate at the top of every hook file:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

Husky v9 no longer needs those two lines, and they are deprecated, so remove them. The legacy husky install command has been replaced by plain husky. If you upgraded from an older version, update the prepare script from husky install to husky and delete the boilerplate from each hook file.

Step 5: Handle Monorepos and Subfolders

Husky must run from the folder that contains .git. If your package.json lives in a subfolder such as frontend/, running husky there cannot set up hooks correctly. Point the prepare script at the parent and tell Husky where the hook folder is:

{
  "scripts": {
    "prepare": "cd .. && husky frontend/.husky"
  }
}

The hook file then lives at frontend/.husky/pre-commit, and any commands inside it run from the repository root, so use cd frontend inside the hook if your tools expect to run from the subfolder.

Step 6: Fix "command not found" in Git GUIs

If hooks run in the terminal but fail with command not found: npx or node: command not found when you commit from VS Code, GitHub Desktop, or another Git client, the GUI is not loading your shell profile. This is common with nvm, fnm, and Volta, which add Node to the path in shell startup files.

Husky supports a per-user init file that runs before every hook. Create ~/.config/husky/init.sh and load your version manager there:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

Adjust the lines for fnm or Volta if that is what you use.

Step 7: Rule Out Skipped Hooks

Two more things silently disable hooks and are easy to forget:

  • The HUSKY=0 environment variable. If it is exported in your shell profile or CI environment, Husky skips every hook. Run echo $HUSKY to check.
  • git commit --no-verify. This flag bypasses the pre-commit hook on purpose. Check your shell aliases and any editor extension that commits on your behalf.

Frequently Asked Questions

Why does the hook run but the commit still succeeds with lint errors?

Your command probably exits with code 0. ESLint only fails the commit when it reports errors, not warnings, unless you pass --max-warnings 0. Add that flag to eslint in your lint-staged config if warnings should block a commit.

Do Husky hook files need to be executable?

Not in v9, because Husky's stub runs them through sh. In v8 and earlier the files needed the executable bit, so old repositories sometimes needed chmod +x .husky/pre-commit.

How do I skip the hook for one commit?

Use git commit --no-verify, or set the variable for a single command with HUSKY=0 git commit -m "message". Keep this for emergencies, since CI should still run the same checks.

Should I run tests in the pre-commit hook?

Usually not. Keep pre-commit fast with lint-staged on changed files only, and move full test suites to pre-push or CI so commits stay quick.

Key Takeaways

When a Husky pre-commit hook does not run, work through the checks in order: run git config core.hooksPath and expect .husky/_, confirm the prepare script actually ran on that machine, make sure .husky/pre-commit exists with the exact name and calls npx lint-staged, and then look at monorepo paths, Node availability in Git GUIs, and the HUSKY variable. Fixing the prepare script with husky || true and committing a correct hook file removes the cause for the whole team, not just your own clone.

#husky #lint-staged #git-hooks #pre-commit #eslint
Share this article:

0 Comments

No comments yet — be the first to share your thoughts.

Leave a comment

Never published.