Husky hooks in GUI clients and IDEs Jump to heading

A developer commits from the terminal and the hooks run perfectly. The same developer commits from their editor’s source-control panel or a desktop Git client, and the commit fails with npx: command not found, or the hook runs with a different Node version and reports nonsense. Nothing is wrong with the hooks. GUI applications are usually started by the desktop environment, not from a shell, so they never load ~/.bashrc or ~/.zshrc — which is where Node version managers like nvm, fnm and asdf add themselves to PATH. The hook runs, cannot find Node, and fails. Husky provides a single place to fix this for every hook. This page shows how, within local hook configuration with Husky.

When to use this approach Jump to heading

  • Hooks work in a terminal but fail in an editor or GUI Git client.
  • Errors mention node, npx or a tool name not being found.
  • Hooks run with an unexpected Node version in some clients.
  • Your team uses Node version managers, which almost always means this problem.

Step 1 — Confirm it is an environment problem Jump to heading

Make the hook print its environment, then commit from both the terminal and the GUI client and compare.

# Temporarily, at the top of .husky/pre-commit
printf 'echo "PATH=$PATH" >> /tmp/hook-env.log; command -v node >> /tmp/hook-env.log 2>&1 || echo "node: not found" >> /tmp/hook-env.log\n' |
  cat - .husky/pre-commit > /tmp/pc && mv /tmp/pc .husky/pre-commit

After one commit from each place, /tmp/hook-env.log shows the difference — typically the terminal PATH includes something like ~/.nvm/versions/node/v20.x/bin and the GUI PATH does not. Remove the debug lines afterwards.

Terminal commit against GUI commitA terminal commit inherits the shell's environment, including PATH entries added by Node version managers in shell startup files. A GUI client is started by the desktop session, never reads those files, and runs hooks with a minimal PATH where node may not exist.TerminalGUI client / editorshell startup filesloadednot loadednvm / fnm / asdf on PATHyesnonode foundyesoften notNode versionproject'ssystem or nonethe hook is identical; only the environment it inherits differs

Step 2 — Add a Husky init script Jump to heading

Husky v9 sources ~/.config/husky/init.sh before running any hook, if the file exists. Put the version-manager setup there, once per machine, and every hook in every repository gets it.

mkdir -p ~/.config/husky
cat > ~/.config/husky/init.sh <<'EOF'
# Loaded by Husky before every hook — make Node available to GUI clients
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" --no-use
nvm use --silent >/dev/null 2>&1 || true        # respects the project's .nvmrc
EOF

For other version managers, the equivalent lines are short.

# fnm
eval "$(fnm env --use-on-cd)"; fnm use --silent-if-unchanged >/dev/null 2>&1 || true
# asdf
. "$HOME/.asdf/asdf.sh"
# volta
export VOLTA_HOME="$HOME/.volta"; export PATH="$VOLTA_HOME/bin:$PATH"
A hook run from a GUI client, with the init scriptThe GUI client calls git commit with a minimal environment. Husky's hook runner sources the per-user init script, which loads the Node version manager and selects the project's version, then runs the hook, which now finds npx.GUI clientgithusky runnerinit.shcommit (minimal PATH)run pre-commitsource ~/.config/husky/init.shnode on PATHnpx lint-staged succeedsone file per machine fixes every repository's hooks

Step 3 — Respect the project’s Node version Jump to heading

The init script should select the version the project declares, not just any version. With nvm, nvm use reads .nvmrc; with fnm and volta, the project’s .node-version or package.json volta field. Make sure the repository declares it.

node --version > .nvmrc          # or set engines and a .node-version file
git add .nvmrc && git commit -m "chore: pin Node version for tooling and hooks"
# Verification: commit from the GUI client and check the version the hook saw
printf 'node --version > /tmp/hook-node-version\n' >> .husky/pre-commit   # temporary

Step 4 — Avoid depending on PATH where possible Jump to heading

The init script fixes the environment, but hooks become more robust if they avoid needing a global node in the first place for some tools, by using paths inside the project.

# .husky/pre-commit — prefer the project-local binary
./node_modules/.bin/lint-staged

This still needs node to execute the script, so it complements the init script rather than replacing it. For non-Node tools — Python linters, Go formatters — call them through the project’s environment manager, as described in lint-staged for Python and Go projects.

Step 5 — Distribute the fix to the team Jump to heading

The init script lives in each person’s home directory, so it must be set up on every machine. Add it to the developer machine bootstrap and to the contributing guide.

# In the team's machine bootstrap script
install -d ~/.config/husky
[ -f ~/.config/husky/init.sh ] || cp tooling/husky-init.sh ~/.config/husky/init.sh

The broader bootstrap is covered in bootstrapping a developer machine for Git.

Step 6 — Handle Windows clients Jump to heading

On Windows, GUI clients run hooks through Git for Windows’ bundled shell. The same init script mechanism applies, but the file is under the Windows user profile, and Node installed by the official installer is usually already on the system PATH. Problems typically come from version managers such as nvm-windows, whose PATH entries are set per session.

# %USERPROFILE%\.config\husky\init.sh  (read by Git for Windows' sh)
export PATH="$APPDATA/nvm/current:$PATH"     # nvm-windows' active version symlink
Which fix does this machine need?If node is not found in GUI commits on macOS or Linux, add the version manager to Husky's init script. If node is found but at the wrong version, make the init script select the project's version file. On Windows with a version manager, add its active-version directory to PATH in the init script.What goes wrong in GUI commits?node not foundInit scriptload the version managerwrong Node versionSelect project versionnvm use / .nvmrcWindows + nvm-windowsPATH in init.shcurrent symlink dirall three fixes live in the same per-user file

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Why not just put the nvm lines in each hook file? Jump to heading

It works, but couples the repository to one version manager and repeats the same lines in every hook. The init script keeps machine-specific setup on the machine and hooks portable.

My editor has a setting to use a login shell. Does that help? Jump to heading

Some editors can start their integrated Git with a login shell, which loads profile files. That fixes the editor but not other GUI clients; the init script fixes all of them.

Is it safe for Husky to source a file from my home directory? Jump to heading

It runs with your permissions, like your shell profile. Keep it to environment setup; anything printed or failing there affects every hook in every repository.