Disabling Git hooks temporarily without breaking the team Jump to heading
Every hook eventually needs to be skipped: a production incident at midnight, a rebase where the formatter fights every intermediate commit, a container image where the hook manager is not installed. The mechanisms all exist, and the reason this needs a page is that people reach for the widest one available — uninstalling hooks, or exporting a disable variable in a shell profile — and then never turn them back on. This recipe matches the bypass to the problem, extending Local Hook Configuration with Husky.
When to use this approach Jump to heading
- An incident needs a fix committed now and the pre-commit checks take ninety seconds.
- A rebase replays thirty commits and the commit-msg hook rejects historical messages written before the convention existed.
- A container image or CI runner has no Node, so a hook manager’s install step fails.
- A generated commit — a release bot, an automated dependency bump — should not be subject to interactive checks.
- If you are bypassing hooks several times a week, the answer is not a better bypass: move the slow checks to
pre-pushor the pipeline, as set out in Pre-Push Validation Rules.
Step 1 — Match the bypass scope to the actual problem Jump to heading
Step 2 — Skip a single command Jump to heading
# Skips pre-commit and commit-msg for this commit only
git commit --no-verify -m "fix: restore payment webhook endpoint
Bypassed local hooks: production incident INC-4821.
Formatting and lint will be corrected in the follow-up."
# Skips pre-push for this push only
git push --no-verify What changed: exactly one command ran without its hooks. The next command has them back, with nothing to remember.
# Prove hooks are still active
git commit --allow-empty -m "chore: confirm hooks still run"
# Expect: the usual hook output Recording the bypass in the commit message costs nothing and is what makes the exception reviewable later. A --no-verify commit with no explanation is indistinguishable from someone who did not know the hooks existed.
Step 3 — Skip for one shell session Jump to heading
A long rebase can trigger a hook once per replayed commit, which is both slow and — for messages written before the convention existed — guaranteed to fail.
# A subshell: the variable dies when the subshell exits
( export HUSKY=0; git rebase -i origin/main )
# Or for a scripted batch of commits
env HUSKY=0 sh scripts/import-legacy-changes.sh # Confirm the variable did not leak into your interactive shell
echo "HUSKY=${HUSKY:-unset}" # expect: unset
git commit --allow-empty -m "chore: hooks active again" SAFETY WARNING — never put
export HUSKY=0in.bashrc,.zshrcor any shell profile. It disables hooks on every repository, in every session, permanently, and nothing will ever remind you. Months later the machine is the one that pushes unformatted code and leaks a credential, and the cause is a line in a dotfile nobody remembers adding. Use a subshell every time.
Step 4 — Disable hooks in automated environments Jump to heading
Automation is a legitimate permanent exemption, because the checks the hooks perform run as pipeline jobs instead.
# Husky v9 skips installation when CI is set to a truthy value
# For runners that do not set it, be explicit:
HUSKY=0 npm ci
# In a Dockerfile
ENV HUSKY=0
RUN npm ci
# A release bot's commit, which should not be subject to interactive checks
- name: Commit the version bump
env:
HUSKY: 0
run: |
git config user.name "release-bot"
git config user.email "[email protected]"
git commit -am "chore(release): v${{ steps.version.outputs.next }}"
What changed: hook installation and execution are skipped where they add nothing, without touching any developer’s machine.
Step 5 — Make sure hooks come back Jump to heading
# A one-line health check worth putting in a task or Makefile target
hooks_ok() {
path=$(git config core.hooksPath || echo .git/hooks)
[ -n "${HUSKY:-}" ] && { echo "HUSKY=$HUSKY is set — hooks are disabled"; return 1; }
[ -x "$path/pre-commit" ] || { echo "no executable pre-commit in $path"; return 1; }
echo "hooks active in $path"
}
hooks_ok # Restore hooks if the check fails
unset HUSKY
npx husky # or: pre-commit install --overwrite
git config core.hooksPath .husky
hooks_ok Wire that check into whatever developers run daily — a make dev target, a project script, the same place your setup instructions live. A disabled hook that nothing notices is the failure mode this whole page exists to prevent, and the fix is a check that runs without anyone deciding to run it.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Is using --no-verify a bad practice? Jump to heading
No — it is the intended escape hatch, and a workflow that makes it necessary daily is telling you the hooks are too slow or too strict. What is bad is the permanent workaround: hooks uninstalled on one machine for months, or a CI variable that disables them everywhere. Prefer the narrowest, shortest-lived bypass, and treat repeated use as a signal to fix the hook.
Does --no-verify skip every hook? Jump to heading
It skips the hooks for the command you ran: pre-commit and commit-msg for git commit, pre-push for git push. It does not skip server-side hooks, which run inside the receiving repository and cannot be influenced by a client flag at all. That is exactly the separation that makes local hooks safe to bypass.
Why do my hooks still not run after re-enabling them? Jump to heading
Usually core.hooksPath still points somewhere empty, or an environment variable that disables the hook manager is exported in a shell profile and outlives the session you set it in. Check git config core.hooksPath and env | grep -i husky before assuming the installation is broken.
Related Jump to heading
- Local Hook Configuration with Husky — the parent guide: installing hooks, pinning versions, and keeping them fast enough that bypassing is rare.
- Server-Side Hook Enforcement — where the rules that must not be skippable belong.
- Pre-Push Validation Rules — moving slow checks off the commit path so the bypass is needed less often.