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-push or the pipeline, as set out in Pre-Push Validation Rules.

Step 1 — Match the bypass scope to the actual problem Jump to heading

Pick the narrowest bypass that solves the problemA single command flag affects one command and reverses itself. A session variable affects one shell and ends when the shell closes. An environment variable in CI affects automated runs only. Uninstalling hooks affects a machine indefinitely and is almost never the right answer.narrowestwidestone command--no-verifyan incident fix,a one-off commitreverses itselfnothing to undoone sessionHUSKY=0in a subshella long rebase,a scripted batchends with the shellnever export it in a profileautomation onlyCI=true / HUSKY=0CI runners, containers,read-only worktreesscoped by environmentthe checks run in CI anywayuninstallrm .git/hooks/*almost never rightsilent for monthsnothing reminds you

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=0 in .bashrc, .zshrc or 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.

Why automation can skip hooks safelyOn a developer machine the hook is the first and fastest feedback. In CI the same checks run as jobs, so the hook adds nothing. In both cases the server-side rules apply regardless, which is what makes the local hook safe to skip at all.developer machinehooks ONfastest possible feedbackCI runnerhooks OFFthe checks are jobs insteadpush to the remotehooks are irrelevant hereserver-side rulesapply either waythe local hook is a convenience; the guarantee lives on the server, which is why skipping it is safe

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
Bypasses that end themselves, and bypasses that do notA command-level bypass covers a single moment and hooks are active again immediately afterwards. A profile-level bypass stays in effect indefinitely with nothing to signal it, until a health check run as part of a daily task surfaces it.--no-verify — self-limitinghooks activeone commitnothing to remember, nothing to undoexport HUSKY=0 in a shell profile — permanenthooks silently off — for monthshealth check finds itthe red band is only as short as the interval between health checks — which is why one should run daily

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.