Husky: Git Hooks via core.hooksPath, and What It Costs You
Git hooks made easy 🐶 woof!
At a glance
- What is it?
- Husky is a 2 kB dependency-free npm package that wires Git's core.hooksPath setting to a hooks directory inside your repository. It is the right tool for teams that already run npm install and want hook scripts versioned with the code, and the wrong tool for anyone who cannot guarantee that install step runs.
- Who is it for?
- Adopt Husky if your project already runs npm install or npm ci as part of setup and you want hook scripts reviewed in pull requests alongside the code they guard. Do not adopt it if your contributors work outside a Node package manager, if CI is the only place you want checks to run, or if you cannot make the prepare script part of the documented setup.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 180 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem Husky solves is hook distribution, not hook execution
Git hooks live in .git/hooks, and that directory is not versioned. Every clone starts with sample files and nothing else. A pre-commit script that runs a linter on one developer's machine has no path to anyone else's machine unless someone writes setup instructions and everyone follows them. Husky's answer is to move the hook directory out of .git and into the repository, then point Git at the new location.
The audience is narrow and specific. If your project is installed with npm, pnpm, yarn or bun, and your contributors run an install command before working, Husky fits. If your contributors clone a repository and open it in an editor without ever touching a package manager, Husky has no install step to hook into. That distinction matters more than any feature list. The README states the package adheres to npm best practices by using the prepare script, which tells you the intended trigger is package installation and not a separate command you have to remember.
core.hooksPath is the whole mechanism
Git added core.hooksPath as a configuration setting that overrides where hooks are read from. Husky uses it. During installation, Husky sets that config value to point at a hooks directory inside the project. Git then runs whatever scripts it finds there, using its normal hook names: pre-commit, commit-msg, pre-push and the rest.
This design choice explains most of Husky's properties. The README lists support for all 13 client-side Git hooks, and that number comes directly from Git's own hook list rather than from anything Husky implements. The package does not intercept commits, does not wrap Git, and does not run a daemon. It writes a config value and a directory of shell scripts. The README describes the package as adhering to Git's native hook organization, which is an accurate way to put it: the scripts you write are ordinary Git hooks, and Git is the thing that decides when they run.
The stated size is 2 kB gzipped with no dependencies, and the README claims it runs in roughly 1ms. That figure is a claim from the project, not something this article measured. It is plausible given the mechanism: setting core.hooksPath costs nothing at commit time, and the hook itself is a shell script whose runtime is entirely yours to determine. If your pre-commit hook runs a full test suite, Husky adds no measurable overhead and your test suite adds all of it.
Installing it: init, prepare, and the hooks directory
The README points to the documentation site at typicode.github.io/husky for install instructions, and it does not reproduce the exact command sequence. What the README does establish is the shape of the setup. Husky is installed as a development dependency, and the prepare script in package.json is the trigger. The README frames this as alignment with npm best practices, and npm's own guidance is that prepare runs on local install and before publish.
An init command exists. The README references husky init indirectly through the v9 changelog link and through the migration note, and the repository's own tooling is a reasonable guide: the CI badge points at a Node.js CI workflow, so the project itself is exercised through GitHub Actions on Node.
The practical consequence of the prepare-script approach is that a fresh clone followed by npm install sets core.hooksPath automatically. No extra step. That is the feature. It is also the failure mode, covered below.
Two configuration surfaces are worth naming because the README calls them out. The first is the hooks directory location, which the README lists as a supported scenario alongside nested projects and monorepos. The second is branch-specific hooks, which the README lists as a feature without describing the mechanism. If you need a hook that behaves differently on a release branch than on a feature branch, the documentation is where you would look, and this article cannot confirm the exact API from the README.
The prepare script is a single point of failure
Husky runs when prepare runs. If prepare does not run, core.hooksPath is never set, and every hook in the repository is silently inert. Git does not warn you about a hooks directory that is not configured. Commits succeed. Nothing tells the developer that the checks they think are running are not running.
Several ordinary situations produce this. Installing with a flag that skips scripts disables prepare. Installing in a CI environment where the package manager is configured to ignore lifecycle scripts disables prepare. Cloning and working without running an install at all disables prepare. In a monorepo, running an install at the workspace root may or may not execute the prepare script of a nested package, depending on the package manager's behavior, and the README lists monorepos as supported without explaining the boundary.
The failure is quiet, which is the part that should worry you. A hook that fails loudly when it finds a lint error is doing its job. A hook that never runs looks identical to a hook that passes. If your team treats pre-commit checks as a quality gate, understand that Husky's gate is only closed on machines where prepare has run at least once, and that a developer can reopen it by reinstalling with scripts disabled.
This is not a defect in Husky so much as a property of Git hooks in general. Hooks are local. They are a convenience for the person committing, not an enforcement mechanism for the repository. If you need the check to be guaranteed, it has to run in CI, where you control the environment.
Husky versus calling Git directly, or using a non-Node hook manager
The baseline alternative is no tool at all: commit a shell script, tell contributors to copy it into .git/hooks, and document the step. That works and adds zero dependencies. It also decays. Contributors change machines, forget the copy step, and the hooks directory drifts out of sync with the repository version. Husky's value is that the copy step becomes automatic for anyone who installs the package, and the scripts stay under version control where they can be reviewed.
The more interesting alternative is a hook manager that does not depend on a package manager at all. Tools in that category install hooks through a standalone binary and store configuration in their own file format. The difference in approach is where the hook logic lives. Husky keeps your logic in POSIX shell scripts that Git itself executes, which the README lists as a supported way to script advanced cases. A manager with its own config format keeps logic in that format and generates the shell script for you. The config format is easier to write for common cases and harder to escape when you need something Git-adjacent. Shell scripts are the opposite: no abstraction, no help, but nothing between you and the exit code.
Husky's second differentiator is that its hooks are Git's hooks. There is no intermediate runtime deciding which hooks exist or how they are invoked. If you already know how a commit-msg hook works, you know how a Husky commit-msg hook works. That is a real advantage for anyone who has debugged a hook manager's abstraction layer, and a real disadvantage if you wanted the manager to handle cross-platform shell differences for you.
Upgrading from v4 to v9 is a migration, not a version bump
The README carries a warning in bold: upgrading from v4 to v9 requires migrating previous config. That is an unusually large gap. The v9 changelog is linked separately, which suggests the changes are substantial enough to warrant their own document. The release history in the repository shows v9.1.5, v9.1.6 and v9.1.7 landing between August and November 2024, a steady patch cadence after the major version.
The last push to the repository is dated March 2026, so the project is active, but the most recent release in the repository is v9.1.7 from November 2024. That gap is worth noting without reading too much into it: a Git hook manager has little surface area to change, and a stable release line is not the same as an abandoned one. Still, if you are evaluating Husky for a long-lived project, check the repository directly for releases after v9.1.7 rather than relying on this snapshot.
The licence is MIT. That permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. It is a permissive licence with no copyleft obligation and no patent grant clause. This is a description of the licence text, not legal advice; if your organisation has a policy on dependency licences, route it through whoever owns that policy.
Maintenance cost for Husky itself is close to zero, since there is no runtime to keep patched and no dependencies to audit. The cost sits in your hook scripts, which are ordinary shell and will need the same care as any other script in the repository.
Who should install Husky, and what to check before you do
Install it if your project is a Node package, your contributors run an install command as a matter of habit, and you want lint or format checks to run before a commit is created rather than after it is pushed. The 2 kB footprint and the absence of dependencies mean there is very little to justify not trying it in that setting.
Skip it if your hooks need to be enforced rather than suggested. Skip it if your contributors do not use a Node package manager, because there is no prepare script to run. Skip it if you are on Git v4-era Husky configuration and not prepared to do the migration, since the README is explicit that v4 to v9 requires config changes.
Two things to verify before rolling it out. First, confirm that husky init writes the prepare script into the package.json that your contributors actually install from. In a monorepo, that may not be the root manifest. Second, confirm your Git version supports core.hooksPath, because the entire mechanism rests on that setting and there is no fallback path described in the README. If either check fails, Husky will install cleanly and do nothing, and you will find out when a broken commit reaches main.
Editorial conclusion
Adopt Husky if your project already runs npm install or npm ci as part of setup and you want hook scripts reviewed in pull requests alongside the code they guard. Do not adopt it if your contributors work outside a Node package manager, if CI is the only place you want checks to run, or if you cannot make the prepare script part of the documented setup. Before rolling it out, verify two things: that husky init writes the prepare script into the package.json your contributors actually use, and that your Git version supports core.hooksPath, since that setting is the entire mechanism.
Community notes