Preflight Checklist
What's Wrong?
When Claude Code creates a worktree, the harness writes the following into the per-worktree config file ($GIT_COMMON_DIR/worktrees/<name>/config.worktree):
[core]
longpaths = true
hooksPath = D:\\projects\\invoiceflow-AI\\frontend\\.husky\\_
Two issues with the hooksPath value:
- It is an absolute path pointing at the main repo's hooks directory, not the worktree's. The worktree lives at
D:\projects\invoiceflow-AI\.claude\worktrees\awesome-thompson-7c0a94\, but hooksPath points back at D:\projects\invoiceflow-AI\frontend\.husky\_.
- It is written even when the shared
$GIT_COMMON_DIR/config already specifies a correct relative core.hooksPath (in our case frontend/.husky/_, set by Husky's prepare script). The per-worktree value silently overrides the shared one because extensions.worktreeConfig = true is enabled by the same harness, and worktree-scoped config wins.
Net effect for any project using Husky (or any tool that installs a repo-relative core.hooksPath): hooks become dormant inside Claude-created worktrees. Git follows the absolute path, and one of two things happens:
- The absolute path doesn't exist in the worktree (most common, since
npm install typically only runs in the worktree's own frontend/ and the main checkout's frontend/.husky/_ may not exist or be stale). Git silently runs zero hooks — core.hooksPath does not fall back to anything when its target is missing.
- The absolute path does exist in the main checkout. Git runs hooks from the main repo's directory while operating on the worktree's index. Binary resolution (
npx, lint-staged, project-local Node version) drifts to wherever the main checkout's node_modules resolves, not the worktree's. Hooks "fire" but against the wrong context.
Either way, the protection chain the project author installed is bypassed for every PR generated from a Claude-created worktree, with no warning surfaced to the user.
What Should Happen?
One of the following:
- Preferred: Do not write
core.hooksPath into the per-worktree config at all. If the shared .git/config has a relative core.hooksPath (as Husky and most repo-hook tools install it), git already resolves it correctly per-worktree by interpreting the path relative to the worktree root — no override is needed.
- Acceptable: If the harness must write a path (e.g., to handle bare-checkout edge cases), write a worktree-relative value or an absolute path resolved against the actual worktree root, not the main repo's root.
Error Messages/Logs
None. The failure is silent.
Steps to Reproduce
- Start with any repo whose shared
.git/config already has core.hooksPath set to a repo-relative path (typical of Husky 9+: frontend/.husky/_ or .husky/_).
- From a Claude Code session, create a worktree (e.g., via the harness's automatic worktree-on-task behavior,
claude --worktree, or the EnterWorktree tool).
- Inspect the per-worktree config:
cat $GIT_COMMON_DIR/worktrees/<name>/config.worktree
- Observe:
core.hooksPath is set to an absolute path.
- The absolute path is derived from the main repo's directory, not the worktree's.
- From the worktree, run
git config core.hooksPath and git rev-parse --git-path hooks to confirm the worktree-scoped value overrides the shared one.
- Attempt a commit from the worktree. Husky's
pre-commit does not fire (or fires from the main checkout's context, depending on whether the main frontend/.husky/_ directory happens to exist on disk).
Evidence
Run from inside a Claude-created worktree at D:\projects\invoiceflow-AI\.claude\worktrees\awesome-thompson-7c0a94\:
$ git config --worktree --list
core.longpaths=true
core.hookspath=D:\projects\invoiceflow-AI\frontend\.husky\_
$ git rev-parse --git-path hooks
D:\projects\invoiceflow-AI\frontend\.husky\_
$ test -d D:/projects/invoiceflow-AI/.claude/worktrees/awesome-thompson-7c0a94/frontend/.husky/_ && echo EXISTS || echo MISSING
MISSING
$ test -d D:/projects/invoiceflow-AI/frontend/.husky/_ && echo EXISTS || echo MISSING
EXISTS
The hooks resolve into the main checkout's husky directory while the worktree's own frontend/.husky/_ does not exist — the dormancy/cross-context behavior.
The bug is systematic across worktrees on the same machine:
$ for w in affectionate-cray-18805d awesome-thompson-7c0a94 cool-solomon-082b06 cranky-mirzakhani-663c1b distracted-hamilton-3bbfec; do
echo "--- $w ---"
cat $GIT_COMMON_DIR/worktrees/$w/config.worktree
done
--- affectionate-cray-18805d ---
[core]
longpaths = true
hooksPath = D:\\projects\\invoiceflow-AI\\frontend\\.husky\\_
--- awesome-thompson-7c0a94 ---
[core]
longpaths = true
hooksPath = D:\\projects\\invoiceflow-AI\\frontend\\.husky\\_
--- cool-solomon-082b06 ---
[core]
longpaths = true
hooksPath = D:\\projects\\invoiceflow-AI\\frontend\\.husky\\_
--- cranky-mirzakhani-663c1b ---
[core]
longpaths = true
hooksPath = D:\\projects\\invoiceflow-AI\\frontend\\.husky\\_
--- distracted-hamilton-3bbfec ---
[core]
longpaths = true
hooksPath = D:\\projects\\invoiceflow-AI\\frontend\\.husky\\_
Shared .git/config (untouched, correct):
[core]
repositoryformatversion = 0
...
hooksPath = frontend/.husky/_
[extensions]
worktreeConfig = true
These earlier reports flagged that the harness wrote core.hooksPath into the shared $GIT_COMMON_DIR/config, which corrupted the main checkout. The harness now correctly scopes the write to per-worktree config.worktree — addressing the #27474 concern — but the value it writes is still derived from the main repo's directory rather than the worktree's. The end-user impact has moved from "main checkout broken" to "every Claude worktree silently has dormant or cross-context hooks." It is the same root cause (the path is computed from the wrong base directory) surfacing in a new file.
Impact
For any project using Husky, lefthook, pre-commit, or any repo-hook tool that installs a repo-relative core.hooksPath:
pre-commit, commit-msg, pre-push, post-merge, etc. do not run inside Claude-created worktrees.
- Whatever those hooks were meant to enforce (typecheck, lint, secret scanning, schema-drift checks, build gates, conventional-commits, signing) is silently bypassed for every commit Claude makes.
- No warning is surfaced to the user. The first signal is typically CI failing on something local hooks would have caught — or in worse cases, a bug landing because the main checkout's hooks pass against stale code in a different directory.
Suggested Fix
The simplest and least-invasive fix:
-# Currently in $GIT_COMMON_DIR/worktrees/<name>/config.worktree
-[core]
- longpaths = true
- hooksPath = <absolute path to main repo's hooks dir>
+# Proposed:
+[core]
+ longpaths = true
+# (omit core.hooksPath entirely — let it resolve from the shared config)
If the harness has a use case that genuinely requires per-worktree core.hooksPath, derive the absolute path from the worktree's own root (e.g., the path passed to git worktree add), not the main checkout. The longpaths = true setting is independently fine and can remain.
Claude Model
Not sure / Multiple models (harness bug, model-independent).
Is this a regression?
No, this never worked. The failure mode shifted from #27474 (writes corrupted the shared .git/config) to its current shape (writes a bad value into per-worktree config.worktree), but hooks have never resolved correctly inside Claude-created worktrees for repos using a repo-relative core.hooksPath.
Last Working Version
N/A — never worked.
Claude Code Version
2.1.131 (Claude Code)
Platform
Anthropic API
Operating System
Windows (build 10.0.26200, Windows 11 Pro).
Terminal/Shell
PowerShell (PS 5.1; Claude Code's Bash tool also reproduces from /usr/bin/bash via git-bash).
Additional Information
Reproduction repo layout (for context):
- Main checkout:
D:\projects\invoiceflow-AI\
- Husky installed at:
frontend/.husky/ with prepare script cd .. && husky frontend/.husky (per Husky 9's subdirectory pattern: https://typicode.github.io/husky/how-to.html)
- Shared
core.hooksPath (correct): frontend/.husky/_
- Worktrees:
D:\projects\invoiceflow-AI\.claude\worktrees\<name>\
- Git version: 2.51.0.windows.1
- Node version: v22.19.0
Preflight Checklist
claude --worktreeoverwrites thecore.hooksPathof$GIT_COMMON_DIR/config#27474 (writes to shared$GIT_COMMON_DIR/config) and [Bug] Worktree setup modifies main repo git config instead of worktree-specific config #30251 (duplicate of [BUG]claude --worktreeoverwrites thecore.hooksPathof$GIT_COMMON_DIR/config#27474, closed). This report covers a different file and a different failure mode — see "Relationship to [BUG]claude --worktreeoverwrites thecore.hooksPathof$GIT_COMMON_DIR/config#27474" below.What's Wrong?
When Claude Code creates a worktree, the harness writes the following into the per-worktree config file (
$GIT_COMMON_DIR/worktrees/<name>/config.worktree):Two issues with the
hooksPathvalue:D:\projects\invoiceflow-AI\.claude\worktrees\awesome-thompson-7c0a94\, buthooksPathpoints back atD:\projects\invoiceflow-AI\frontend\.husky\_.$GIT_COMMON_DIR/configalready specifies a correct relativecore.hooksPath(in our casefrontend/.husky/_, set by Husky'spreparescript). The per-worktree value silently overrides the shared one becauseextensions.worktreeConfig = trueis enabled by the same harness, and worktree-scoped config wins.Net effect for any project using Husky (or any tool that installs a repo-relative
core.hooksPath): hooks become dormant inside Claude-created worktrees. Git follows the absolute path, and one of two things happens:npm installtypically only runs in the worktree's ownfrontend/and the main checkout'sfrontend/.husky/_may not exist or be stale). Git silently runs zero hooks —core.hooksPathdoes not fall back to anything when its target is missing.npx,lint-staged, project-local Node version) drifts to wherever the main checkout'snode_modulesresolves, not the worktree's. Hooks "fire" but against the wrong context.Either way, the protection chain the project author installed is bypassed for every PR generated from a Claude-created worktree, with no warning surfaced to the user.
What Should Happen?
One of the following:
core.hooksPathinto the per-worktree config at all. If the shared.git/confighas a relativecore.hooksPath(as Husky and most repo-hook tools install it), git already resolves it correctly per-worktree by interpreting the path relative to the worktree root — no override is needed.Error Messages/Logs
None. The failure is silent.
Steps to Reproduce
.git/configalready hascore.hooksPathset to a repo-relative path (typical of Husky 9+:frontend/.husky/_or.husky/_).claude --worktree, or theEnterWorktreetool).core.hooksPathis set to an absolute path.git config core.hooksPathandgit rev-parse --git-path hooksto confirm the worktree-scoped value overrides the shared one.pre-commitdoes not fire (or fires from the main checkout's context, depending on whether the mainfrontend/.husky/_directory happens to exist on disk).Evidence
Run from inside a Claude-created worktree at
D:\projects\invoiceflow-AI\.claude\worktrees\awesome-thompson-7c0a94\:The hooks resolve into the main checkout's husky directory while the worktree's own
frontend/.husky/_does not exist — the dormancy/cross-context behavior.The bug is systematic across worktrees on the same machine:
Shared
.git/config(untouched, correct):Relationship to #27474 / #30251
These earlier reports flagged that the harness wrote
core.hooksPathinto the shared$GIT_COMMON_DIR/config, which corrupted the main checkout. The harness now correctly scopes the write to per-worktreeconfig.worktree— addressing the #27474 concern — but the value it writes is still derived from the main repo's directory rather than the worktree's. The end-user impact has moved from "main checkout broken" to "every Claude worktree silently has dormant or cross-context hooks." It is the same root cause (the path is computed from the wrong base directory) surfacing in a new file.Impact
For any project using Husky, lefthook, pre-commit, or any repo-hook tool that installs a repo-relative
core.hooksPath:pre-commit,commit-msg,pre-push,post-merge, etc. do not run inside Claude-created worktrees.Suggested Fix
The simplest and least-invasive fix:
If the harness has a use case that genuinely requires per-worktree
core.hooksPath, derive the absolute path from the worktree's own root (e.g., the path passed togit worktree add), not the main checkout. Thelongpaths = truesetting is independently fine and can remain.Claude Model
Not sure / Multiple models (harness bug, model-independent).
Is this a regression?
No, this never worked. The failure mode shifted from #27474 (writes corrupted the shared
.git/config) to its current shape (writes a bad value into per-worktreeconfig.worktree), but hooks have never resolved correctly inside Claude-created worktrees for repos using a repo-relativecore.hooksPath.Last Working Version
N/A — never worked.
Claude Code Version
2.1.131 (Claude Code)
Platform
Anthropic API
Operating System
Windows (build 10.0.26200, Windows 11 Pro).
Terminal/Shell
PowerShell (PS 5.1; Claude Code's Bash tool also reproduces from
/usr/bin/bashvia git-bash).Additional Information
Reproduction repo layout (for context):
D:\projects\invoiceflow-AI\frontend/.husky/withpreparescriptcd .. && husky frontend/.husky(per Husky 9's subdirectory pattern: https://typicode.github.io/husky/how-to.html)core.hooksPath(correct):frontend/.husky/_D:\projects\invoiceflow-AI\.claude\worktrees\<name>\