[BUG] Windows: worktree setup resolves relative core.hooksPath against the project dir, not the repo root — silently disables all git hooks

Status Fixed / completed
Reported on v2.1.217
Maintainer reply None cached
Activity 1 comment · opened Jul 23, 2026 · closed Aug 25, 2026

Preflight Checklist

  • [x] I have searched existing issues. Closest is #60620 (closed as not-planned/stale) — same subsystem, but there the project directory was the repo root, so the absolute path it wrote happened to be valid and only the "points at the main repo" symptom was visible. This report identifies the actual defect in the path computation, which additionally produces a path that exists nowhere. Also related but a different file: #27474 / #66993 / #72714 write into the shared $GIT_COMMON_DIR/config; this one is the per-worktree config.worktree.
  • [x] This is a single bug report.
  • [x] I am using the latest version of Claude Code (2.1.217).

What's Wrong?

When Claude Code creates a worktree, it converts the main repo's relative core.hooksPath to an absolute path and writes it into $GIT_COMMON_DIR/worktrees/<name>/config.worktree. The conversion resolves the relative value against the project/session directory instead of the repo top-level.

When the project directory is not the repo root — a monorepo where you open a subdirectory as the project — the result is a corrupt path with a duplicated segment:

repo root                  : D:\factory
project dir (session cwd)  : D:\factory\smisweb
.git/config                : core.hooksPath = smisweb/smisvue/.husky/_   (written by husky v9 `prepare`)
written to config.worktree : D:\factory\smisweb\smisweb\smisvue\.husky\_   ← "smisweb" twice; exists nowhere

Git treats a missing core.hooksPath directory as "hook not configured" and skips it without any error. So every hook silently stops running in that worktree: commits made there — by the agent or by the user — bypass every pre-commit / pre-merge-commit guard the repository installs. In our case that included the guard that blocks commits to main and the entire frontend pre-commit gate. Nothing in the UI or logs indicates the protections are gone.

The relevant code in the bundled CLI (2.1.217), recovered from the minified bundle and trimmed to the relevant lines — names are the minifier's. Note it reads the config from the repo's common dir (i) but resolves against e, the project dir:

async function oes(e, t) {          // e = project dir, t = new worktree path
  let o = await OR(e), i = o ? await eJ(o) ?? o : null,
      s = i ? await $zt(i, "core", null, "hooksPath") : null, a = null;
  if (s) {
    if (a = Ea.isAbsolute(s) ? s : Ea.resolve(e, s), s !== a) {        // ← Ea.resolve(e, s)
      let { code: u, stderr: d } = await qn(co(), ["config", "core.hooksPath", a], { cwd: t });
      if (u === 0) T(`Configured worktree to use hooks from main repository: ${a}`);
      else T(`Failed to configure hooks path: ${d}`, { level: "error" });
    }
  }
  ...
}

Ea.resolve(e, s) needs the main worktree's top-level as its base, not e.

A second, independent failure mode with the same net effect. When no override is written at all, the worktree inherits the relative value, which git resolves against the worktree's own top-level. For husky that points at .husky/_ — a generated directory that husky's own installer marks gitignored (_/.gitignore containing *), so it is never checked out into a new worktree. The "write nothing" path is therefore also silently hook-less unless someone happens to run npm install inside that worktree. We hit both shapes in the same repo: of three linked worktrees, two were silently gate-less and the third only worked by accident of a stray npm install.

What Should Happen?

  1. Resolve the relative value against the main worktree top-level (e.g. the first entry of git worktree list --porcelain, or git rev-parse --show-toplevel in the main checkout), not the session/project directory.
  2. Verify the resolved directory exists before writing it. This is the part that turns a misconfiguration into an invisible loss of every commit-time protection. If the target doesn't exist, skip the write and warn rather than persisting a path that silently disables hooks.
  3. Given the husky case above, consider warning whenever a newly created worktree ends up with a core.hooksPath that resolves to nothing — for repos that rely on hooks as a safety gate, failing open silently is the worst outcome.

Error Messages/Logs

None — the silence is the bug. Probes:

$ git -C <worktree> config --show-origin --get core.hooksPath
file:D:/factory/.git/worktrees/<name>/config.worktree    D:\factory\smisweb\smisweb\smisvue\.husky\_

$ git -C <worktree> hook run pre-commit
error: cannot find a hook named pre-commit

$ git -C <worktree> hook run --ignore-missing pre-commit
                                     # silent, exit 0 — this is what git does during a real commit

$ git -C <main checkout> hook run --ignore-missing pre-commit
[no-commit-to-main] ...              # hooks are fine in the main checkout

Steps to Reproduce

  1. Take a monorepo at <root> where the directory you open in Claude Code is a subdirectory, <root>/app.
  2. Install husky v9 under <root>/app/web, so its prepare script writes a repo-root-relative value into .git/config: core.hooksPath = app/web/.husky/_.
  3. Open a Claude Code session with <root>/app as the project directory, and create a worktree (EnterWorktree, claude --worktree, or automatic worktree isolation).
  4. Inspect the per-worktree config:

``
cat $GIT_COMMON_DIR/worktrees/<name>/config.worktree
`
hooksPath = <root>\app\app\web\.husky\_app` appears twice.

  1. git -C <worktree> hook run --ignore-missing pre-commit → silent, exit 0.
  2. git -C <worktree> commit -m x → no hook runs; the repo's pre-commit gate is bypassed with no warning.

Step 1 is what separates this from #60620: when the project directory is the repo root, resolve(projectDir, relative) coincidentally yields the correct path, so only the milder "hooks point at the main repo" symptom appears.

Claude Code Version

2.1.217

Platform

Claude Desktop (Windows)

Is this a regression?

Not sure — first observed on 2.1.217.

Environment

  • Windows 11 Pro 10.0.26200
  • git 2.54.0.windows.1
  • node v26.1.0
  • husky 9.1.7

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗