Husky creates files for all of the .git/hooks so that instead of having to install a commit hook and write it in bash, you can write it right in your package.json and the commit hooks will run that command.

Let's look at what files are in .git/hooks

$ ls .git/hooks
applypatch-msg.sample pre-applypatch.sample pre-receive.sample
commit-msg.sample pre-commit.sample prepare-commit-msg.sample
fsmonitor-watchman.sample pre-push.sample update.sample
post-update.sample pre-rebase.sample

Now let's install Husky as a dev dependency:

yarn add -D husky

I see this in your package.json:

"eslint": "^5.13.0",
+ "husky": "^1.3.1"

And if I look in .git/hooks again:

$ ls .git/hooks
applypatch-msg post-rewrite pre-rebase
applypatch-msg.sample post-update pre-rebase.sample
commit-msg post-update.sample pre-receive
commit-msg.sample pre-applypatch pre-receive.sample
fsmonitor-watchman.sample pre-applypatch.sample prepare-commit-msg
post-applypatch pre-auto-gc prepare-commit-msg.sample
post-checkout pre-commit push-to-checkout
post-commit pre-commit.sample sendemail-validate
post-merge pre-push update
post-receive pre-push.sample update.sample

if we take a peek at the pre-commit file:

cat .git/hooks/pre-commit
# husky

# Hook created by Husky
# Version: 1.3.1
# At: 2019-3-27 23:05:27
# See: <>

# From npm package
# Name: husky
# Directory: /Users/chaseadamsio/src/
# Homepage: <>

hookName=`basename "$0"`

debug() {
[ "${HUSKY_DEBUG}" = "true" ] && echo "husky:debug $1"

debug "$hookName hook started..."

if ! command -v node >/dev/null 2>&1; then
echo "Can't find node in PATH, trying to find a node binary on your system"

if [ -f "$scriptPath" ]; then
# if [ -t 1 ]; then
# exec < /dev/tty
# fi
if [ -f ~/.huskyrc ]; then
debug "source ~/.huskyrc"
source ~/.huskyrc
node_modules/run-node/run-node "$scriptPath" $hookName "$gitParams"
echo "Can't find Husky, skipping $hookName hook"
echo "You can reinstall it using 'npm install husky --save-dev' or delete this hook"

You can see it does the following:

  • checks if node exists (because otherwise it wouldn't work)
  • checks if the file for husky/run.js exists
    • then checks for a user level ~/.huskyrc and if it exists, sources it
    • runs node on husky/run with "pre-commit" and all the git parameters

So the really cool thing is that it handles setting up all of these hooks so that they can run if you decide to add one.

Let's add a pre-commit hook to run lint-staged.

Before we can setup the pre-commit hook, we need to install lint-staged:

yarn add -D lint-staged
"devDependencies": {
"eslint": "^5.13.0",
"husky": "^1.3.1",
+ "lint-staged": "^8.1.5"
"husky": {
"hooks": {
"pre-commit": "lint-staged"

Now let's add the pre-commit hook to run lint-staged. First, we need to add the lint-staged config:

"devDependencies": {
"eslint": "^5.13.0",
"husky": "^1.3.1",
"lint-staged": "^8.1.5"
+ "lint-staged": {
+ "**/*.{js,jsx}": "yarn test:lint"
+ },

Then we need to tell Husky that we want to run lint-staged on pre-commit hooks:

"lint-staged": {
"**/*.{js,jsx}": "yarn test:lint"
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }

Now, to verify it's working, I'm going to remove a semi-colon from my gatsby-node.js. This will cause ESLint to throw an error:

-  let frontmatter = {};
+ let frontmatter = {}

Now I'm going to stage gatsby-node.js with git add gatsby-node.js and run:

npx lint-staged

This will run lint-staged with our configuration and we can see if it does what we want it to.

and sure enough Husky failed our commit as we expected:

↓ Stashing changes... [skipped]
→ No partially staged files found...
❯ Running linters...
❯ Running tasks for **/*.{js,jsx}
yarn test:lint

yarn test:lint found some errors. Please fix them and try committing again.
yarn run v1.13.0
$ eslint --ignore-path .gitignore --ext .js,.jsx . /Users/chaseadamsio/src/

60:6 error Missing semicolon semi

1 problem (1 error, 0 warnings)
1 error and 0 warnings potentially fixable with the `--fix` option.

info Visit <> for documentation about this command.

error Command failed with exit code 1.
posted on April 5th 2019