Git Commits Succeed but Husky Hooks Silently Fail: The Worktree Trap and a Multi-Layer Defense

Git worktrees inherit the hook path but lack executable shims, causing pre-commit checks to be silently skipped.
The author describes a silent failure when using Husky with Git worktrees: a new worktree inherits core.hooksPath but doesn't generate the executable shims under .husky/_, so pre-commit checks never run while commits still succeed silently. This shows that "configured hooks" doesn't mean "hooks in effect." With parallel Cursor sessions and cloud agent fleets, every new worktree becomes a separate initialization boundary. The fix splits hook safeguards into four layers: availability verification, agent edit feedback, local pre-commit, and CI backup, using shared scripts and startup checks to catch broken states before they slip through.
A trap that makes code checks meaningless
I've gotten into the habit of using Husky across multiple repositories so that every commit goes through lint, type checking, and formatting validation. This setup ran reliably for years—until I added a git worktree.
The new worktree inherited the main checkout's core.hooksPath config, but it did not inherit the executable shims Husky generates under .husky/_. The result: I committed code, Git happily accepted it, and the commit succeeded. Not a single pre-commit script ran. No errors, no red warnings, no failing exit code. Those mandatory checks I trusted implicitly had never run.
That's when I realized I had been equating "hooks are configured" with "hooks are in effect"—and that's wrong.

What "configured" actually means
Modern Husky no longer drops scripts directly into .git/hooks. Git reads core.hooksPath (usually pointing to .husky/_), and Husky installs small forwarding shims there that then invoke the real hook scripts, such as .husky/pre-commit.
The key problem: a new worktree inherits the repository's core.hooksPath setting, but it does not automatically generate the executable shims under .husky/_. git worktree add won't do that for you.
Existing checkout
├─ core.hooksPath → .husky/_
└─ .husky/_ shims present → pre-commit runs
New worktree
├─ core.hooksPath → .husky/_ (inherited)
└─ .husky/_ shims missing → commit succeeds, hooks silent
In other words, a brand-new worktree can absolutely have the correct hook path configured while lacking the executable shims needed to run the checks. Git still accepts your commit, and everything looks calm on the surface.
core.hooksPath is a Git config option used to override the default hook lookup directory (.git/hooks). When it's set to a path, Git goes directly to that path when executing hook stages like pre-commit, commit-msg, and pre-push, looking for executables with matching names—and completely ignores .git/hooks.
This mechanism is meant to let hook scripts be version-controlled and reused across machines, but it comes with a precondition: the target path must actually contain executable files. Git is only responsible for "look here," not for "make sure something is here." If the path exists but files are missing or lack execute permission, Git treats it as having no hooks and silently continues with the commit.
Understanding this is the key to the whole article: whether it's Husky or any other hook manager, they're only preparing these files on Git's behalf. The configuration itself and whether the files exist are two independent things.
When the problem escalates from a single soldier to an agent fleet
For the past few years, I've basically worked in a relatively stable checkout environment: install dependencies, initialize Husky, then rarely think about hook configuration again.
But that assumption started to collapse when I began using Cursor sessions and Git worktrees in parallel. Every new worktree is a checkout that needs its hooks initialized independently—even if the original checkout was configured perfectly.
When I started designing a cloud agent fleet coordinated by GrokBot, the problem got even trickier. GrokBot runs on a cloud computer, dispatching coding tasks to Cursor cloud agents in their own separate environments.
A cloud agent can start up fine, run the install script, and initialize Husky. But if it then creates a Git worktree, that new checkout needs its own initialization. The startup script won't automatically run again.
The original checkout is initialized correctly; the new worktree isn't. And in a fleet of multiple agents, I can't rely on myself to spot and fix every missing hook.
Neither Git nor Husky changed. What changed was me—I started treating agent startup as the boundary of initialization, when in reality every new worktree introduces a new boundary.
Git worktree is a feature introduced in Git 2.5 that allows multiple working directories to be checked out simultaneously on the same repository object store. It's commonly used for parallel development of different branches or running multiple builds at once. What's shared is the .git object store and configuration, but each worktree has its own working files and independent checkout state.
This is often misunderstood: many toolchain initialization steps (installing dependencies, generating hook shims, writing local config) target "a specific checkout directory" rather than "the entire repository." When you create a new worktree, these steps don't automatically replay, so you get a mismatch where config is inherited but files are missing.
In parallel agent or CI environments, this mismatch gets amplified: every worktree is a new initialization boundary, and any assumption that depends on "initialize once" fails at the boundary.
Dividing responsibility across four layers
I need hook availability to be verifiable in every environment, fixable without my intervention, and backed up by checks that don't depend on local hooks running.
I split these responsibilities into four layers:
| Layer | Responsibility |
|---|---|
| 1. Availability | Verify hooks are runnable in this checkout |
| 2. Agent feedback | Improve formatting during editing |
| 3. Pre-commit | Run local validation before commit |
| 4. CI | Independently verify changes |
Blurring the boundaries between these responsibilities is exactly where silent skips hide.
The first layer is observability, not enforcement itself. An agent can't assume hooks are active just because core.hooksPath is set. My verify:git-hooks script checks whether every hook under .husky/ has a corresponding executable shim in this worktree's .husky/_. A green pass only means the pipeline exists—it doesn't prove Git will call these hooks on the next commit, nor that pre-commit checks will pass. Layers three and four are still responsible for "whether the next commit is actually protected."
The second layer deliberately stays weakly enforced. Optional formatting while an agent edits files helps keep diffs readable, but that's convenience, not a guarantee, and it can't replace pre-commit or CI.
Layers three and four are intentionally separate. Local pre-commit is each repository's own chosen recipe; CI is the last line of defense when a checkout, an agent session, or a skipped hook lets something through.
What I changed
I moved hook verification and repair into shared scripts that run at Cursor session startup. They check whether Husky's executable shims exist in the current checkout, attempt to repair them if missing, and emit a HOOKS NOT RUNNABLE warning if the problem persists. Session startup stays fail-open so broken hooks don't block me from continuing to edit.
Cloud agents need another safeguard. Cursor's hook bridge routes Git hook execution to the current repository at commit time. When the bridge is active, missing Husky shims produce an explicit commit failure instead of a silent skip. In repositories where I've configured a blocking cloud startup gate, the agent also waits for the bridge to be available before starting work.
None of this automatically initializes worktrees created later in the session. Every new checkout still needs its own prepare run. I packaged the verification, repair, and cloud lifecycle scripts into my marketplace plugin and reuse them across multiple repositories. But each repository still needs to wire these scripts into its own install and session startup lifecycle—simply installing the plugin won't activate these safeguards.
What changed isn't that all possible hook failures have been eliminated, but that broken hook states become easier to detect, repair, or intercept before an unchecked commit slips through.
Key takeaway: A configured hook path tells me where Git will look, but it doesn't tell me whether there's actually anything there that will run.
Implementation code: My published hook verification and repair scripts are public—feel free to check them out if you're interested in the approach.
"Fail-open" and "fail-closed" are terms describing failure strategies. Fail-open means allowing the process to continue when validation or protection fails, keeping workflow uninterrupted but potentially letting risks slip through; fail-closed means blocking when failure occurs, ensuring safety but potentially causing false positives on normal operations.
The author keeps session startup fail-open to avoid broken hooks hindering editing, while the cloud's "blocking startup gate" leans fail-closed, turning missing shims into explicit commit failures. Together, they handle risk and availability needs at different stages separately, rather than applying a single strategy to all scenarios.
One-line Summary
Git worktrees inherit the hook path but lack executable shims, causing pre-commit checks to be silently skipped.
Paragraph Summary
The article describes a silent failure the author encountered when using Husky with Git worktrees: a new worktree inherits the core.hooksPath config but doesn't generate the executable shims under .husky/_, so pre-commit checks never run while commits still succeed with no errors. This made the author realize that "configured hooks" doesn't equal "hooks in effect." In scenarios with parallel Cursor sessions and cloud agent fleets, every new worktree is an independent initialization boundary, further amplifying the problem. To address this, the author split hook safeguards into four layers: availability verification, agent edit feedback, local pre-commit, and CI backup—using shared scripts, session startup checks, and a cloud hook bridge to make broken states easier to detect, repair, or intercept before commit.
Related articles

Bilibili's Hit Dify Tutorial: A Practical Guide to Building 20+ AI Apps from Scratch
A distilled guide to Bilibili's top-rated Dify tutorial: Windows + Docker deployment, MySQL connection, five app types, model integration strategy, and publishing.

Agent Skills Beginner's Guide: From Concepts to Multi-Agent Collaboration in Practice
A comprehensive guide to Agent Skills: core concepts, components, the importance of Description, standardization, beginner learning paths, and multi-agent collaboration.

One Neuron Detects Both Cats and Cars? We Reproduced It and Found a New Explanation
The cat-car neuron reinterpreted: researcher Cammarata argues it distinguishes pointy from round, not cats from cars. We reproduced the experiment and found the new explanation holds on most inputs—but cars are the exception. The interpretability debate continues on language models.