Preflight Checklist
Not a duplicate of the known shared-config bug — this is the half that is still wrong
#27474, #72714 and #85039 all report worktree creation clobbering the shared config
($GIT_COMMON_DIR/config / .git/config). On 2.1.237 that no longer happens here — the shared
config keeps its correct relative value:
$ git config --local --get core.hooksPath
.husky/_ # ← intact, not rewritten
What is written is a per-worktree override, in the worktree scope #27474 suggested moving to:
$ cat .git/worktrees/<name>/config.worktree
[core]
longpaths = true
hooksPath = /Users/…/<MAIN CHECKOUT>/.husky/_ # ← absolute, points at another checkout
So the destructive half looks fixed. But the path is still absolute, and #27474's author already
called that out: "even git config --worktree is pointless since, if sharing hooks is the goal,
it's already achieved by git-config resolution." That is exactly right, and the absolute form has
its own failure mode, which is the reason for this report.
Consequence
extensions.worktreeConfig = true makes the worktree scope outrank the shared one, and husky's shim
resolves the real script from its own location:
s=$(dirname "$(dirname "$0")")/$n # .husky/_/pre-push -> .husky/pre-push
With $0 absolute, $s is absolute too, so every hook fired from the worktree executes the MAIN
checkout's scripts — on whatever branch that checkout happens to be sitting on. Nothing errors; the
hook simply prints the steps that other branch has.
Two ways it bites, and the second is the dangerous one:
- A PR that ADDS a hook step gets no local exercise. The step never fires. This is how we found
it: a new pre-push step was present in the worktree's .husky/pre-push and simply absent from the
git push output.
- A PR that WEAKENS or REMOVES a hook step still runs the old, stronger hook — so the push goes
green on a change that disabled a gate. For any team whose hooks are the only gate, that is a
silently removed safety net.
A quieter third: whichever branch the main checkout is on defines the gate for every worktree.
Ours was on a feature branch, not main, for the whole period.
Attribution — by control, not by reading
| check |
result |
plain git worktree add (control) |
writes no config.worktree; core.hooksPath resolves to .husky/_ from local scope |
| husky 9.1.7 source |
writes `${dir}/_` — relative — and never mentions longpaths |
git grep across the repo |
nothing tracked writes either key |
| registered tool-made worktrees |
5 of 5 carry the same longpaths + absolute hooksPath pair |
| the one hand-made worktree beside them |
no config.worktree at all |
Repro
- In a repo using husky (so
.git/config has the relative core.hooksPath = .husky/_), create a
worktree via EnterWorktree or an Agent spawn with isolation: "worktree".
- From inside it:
git config --show-scope --get-all core.hooksPath
local .husky/_
worktree /abs/path/to/MAIN/.husky/_ # ← wins
- Add a distinctive
echo to that worktree's .husky/pre-push, commit, push. The line does not
appear — the main checkout's hook ran instead.
Expected
Write nothing. Git already resolves a relative core.hooksPath per worktree, which is what the
repo's shared config asks for. If a value must be written, write the relative one.
Workaround
Per worktree, worktree-scoped so it touches nothing shared:
git config --worktree core.hooksPath .husky/_
⚠️ Only safe when that worktree has its own .husky/_ shim. If it does not, switching to a relative
path aims git at a directory that does not exist and disables hooks entirely — worse than the
bug. We hit that case for real on one worktree.
Secondary observations
Preflight Checklist
claude --worktreeoverwrites thecore.hooksPathof$GIT_COMMON_DIR/config#27474 / bug: /worktree can silently write core.hooksPath into the MAIN repo's shared .git/config, disabling global hooks #72714 / Worktree creation rewrites the repo's shared core.hooksPath to an absolute path #85039 — see the first section: on 2.1.237 the shared config is left intact and the absolute path is written toconfig.worktreeinstead. That variant is not described by any open issue.Version: Claude Code 2.1.237 (macOS arm64)
Not a duplicate of the known shared-config bug — this is the half that is still wrong
#27474, #72714 and #85039 all report worktree creation clobbering the shared config
(
$GIT_COMMON_DIR/config/.git/config). On 2.1.237 that no longer happens here — the sharedconfig keeps its correct relative value:
What is written is a per-worktree override, in the worktree scope #27474 suggested moving to:
So the destructive half looks fixed. But the path is still absolute, and #27474's author already
called that out: "even
git config --worktreeis pointless since, if sharing hooks is the goal,it's already achieved by git-config resolution." That is exactly right, and the absolute form has
its own failure mode, which is the reason for this report.
Consequence
extensions.worktreeConfig = truemakes the worktree scope outrank the shared one, and husky's shimresolves the real script from its own location:
With
$0absolute,$sis absolute too, so every hook fired from the worktree executes the MAINcheckout's scripts — on whatever branch that checkout happens to be sitting on. Nothing errors; the
hook simply prints the steps that other branch has.
Two ways it bites, and the second is the dangerous one:
it: a new pre-push step was present in the worktree's
.husky/pre-pushand simply absent from thegit pushoutput.green on a change that disabled a gate. For any team whose hooks are the only gate, that is a
silently removed safety net.
A quieter third: whichever branch the main checkout is on defines the gate for every worktree.
Ours was on a feature branch, not
main, for the whole period.Attribution — by control, not by reading
git worktree add(control)config.worktree;core.hooksPathresolves to.husky/_from local scope`${dir}/_`— relative — and never mentionslongpathsgit grepacross the repolongpaths+ absolutehooksPathpairconfig.worktreeat allRepro
.git/confighas the relativecore.hooksPath = .husky/_), create aworktree via
EnterWorktreeor anAgentspawn withisolation: "worktree".git config --show-scope --get-all core.hooksPathechoto that worktree's.husky/pre-push, commit, push. The line does notappear — the main checkout's hook ran instead.
Expected
Write nothing. Git already resolves a relative
core.hooksPathper worktree, which is what therepo's shared config asks for. If a value must be written, write the relative one.
Workaround
Per worktree, worktree-scoped so it touches nothing shared:
.husky/_shim. If it does not, switching to a relativepath aims git at a directory that does not exist and disables hooks entirely — worse than the
bug. We hit that case for real on one worktree.
Secondary observations
core.longpaths = trueis written unconditionally on macOS, where it is a no-op (it is a Windowssetting). Harmless, but it is the fingerprint that identified the writer.
pointed at directories that no longer exist, and 4 directories existed with no registration at all.