Worktree provisioning writes Git LFS hooks to a literal dev/null/ directory instead of .git/hooks/
Summary
When Claude Code creates a new linked git worktree (.claude/worktrees/<name>/) for a repository that uses Git LFS, the provisioning step that installs LFS's repository hooks sometimes writes them to a literal, untracked, relative path dev/null/ inside the new worktree's root — instead of the correct shared hooks directory (<main-repo>/.git/hooks/). The resulting junk (dev/null/post-checkout, post-commit, post-merge, pre-push — real, correctly-formed Git LFS hook shell scripts) is inert (never invoked by git, since the worktree's own core.hookspath config correctly points elsewhere) but shows up as untracked clutter in git status.
Environment
- Claude Code CLI on Windows 11, repo working directory
D:\wkspaces\Reveal-Platform(a GitHub Enterprise / git repo using Git LFS). - Git for Windows with Git Bash (MSYS);
git-lfs/3.7.1 (GitHub; windows amd64; go 1.25.1; git b84b3384). - Three
.claude/worktrees/*linked worktrees existed at the time of investigation, created across three separate sessions between 2026-08-26 and 2026-08-29.
Evidence
| Worktree | Created | core.hookspath (worktree-scoped git config) | dev/null/ present? |
|---|---|---|---|
| objective-dijkstra-6ab1cc | 2026-08-26 21:30 | D:\wkspaces\Reveal-Platform\.git\hooks (correct) | Yes — empty directory only |
| reverent-lichterman-d4a0a0 | 2026-08-26 21:30 | (not set) | No |
| relaxed-keller-24b3f9 | 2026-08-29 06:25 | D:\wkspaces\Reveal-Platform\.git\hooks (correct) | Yes — fully populated: post-checkout, post-commit, post-merge, pre-push, each a valid Git LFS hook shim (git-lfs post-checkout "$@", etc.) |
The correlation is exact: the two worktrees that received worktree-scoped core.hookspath configuration (i.e., went through whatever provisioning step installs Git LFS support for a new worktree) also received the dev/null/ junk — one fully populated, one just the empty directory. The one worktree that did not get core.hookspath set (a detached-HEAD worktree, possibly provisioned via a different, lighter-weight code path) has no dev/null/ junk either. This strongly suggests the same provisioning step is responsible for both the (correct) core.hookspath write and the (buggy) dev/null/ hook-file write — most likely as two sequential actions where the hooks-directory target is computed once wrong and once right.
Supporting checks performed:
git config --worktree --listin the affected worktree showscore.hookspathalready set to the fully-correct absolute path — so this is not a live misconfiguration; the stray files are unreachable dead output from a past provisioning step, not something actively causing wrong hook behaviour today.git lfs envrun fresh inside the affected worktree resolvesLocalGitDir=D:\wkspaces\Reveal-Platform\.git\worktrees\relaxed-keller-24b3f9andLocalGitStorageDir=D:\wkspaces\Reveal-Platform\.gitcorrectly — i.e. Git LFS's own path resolution is correct when invoked normally, right now, in a fully-initialised worktree. This points at a timing/ordering issue during provisioning (the buggy write likely happens before the worktree's git plumbing —.gitpointer file, worktree admin dir — is fully in place), not a persistent defect in Git LFS itself.- The real, shared
<main-repo>/.git/hooks/directory already has Git LFS'spost-checkout,post-commit, andpre-pushhooks installed (dated to original repo setup, well before any worktree existed) — but is missingpost-merge. Possibly an unrelated, pre-existing gap from whenever LFS was first installed for this repo; noted here in case it's a related symptom of the same install-path logic. - The empty-vs-fully-populated difference between the two affected worktrees rules out a simple "always writes N files to a hardcoded wrong path" bug — it looks like a race or reordering: in one case the wrong-path write happened to complete before whatever corrects the path, in the other it was interrupted after only creating the directory.
Impact
Low severity, cosmetic. The stray files are outside version control, never executed (the worktree's real core.hookspath is correct), and don't corrupt repo state. But they add noise to git status/git status --untracked-files=all in every new LFS-repo worktree, which a user or an agent doing a "is my workspace clean" pass has to notice, investigate, and manually exclude — exactly what triggered this report.
Suggested fix
Either:
- Skip Git LFS hook (re-)installation entirely when provisioning a new linked worktree — hooks are already shared automatically via the common git dir /
core.hookspathonce the worktree exists, so a fresh per-worktree LFS install is redundant if the main repository already has LFS hooks; or - If a per-worktree LFS install step is intentional (e.g. to guarantee hooks exist even for repos where the main checkout never ran
git lfs install), make sure it resolves the target hooks directory after the worktree's own git plumbing (andcore.hookspath) is fully established — e.g. viagit rev-parse --git-common-diror by reading backcore.hookspath— rather than a value that can resolve to a bare relativedev/nullwhen the worktree isn't fully set up yet; and use an absolute path rather than one resolved relative to an assumed working directory.
Repro notes for whoever picks this up
Not independently reproducible on demand from outside the harness (this is provisioning logic internal to the Claude Code CLI, not something scriptable from a user session) — the evidence above is what's observable after the fact from two independently-provisioned worktrees of the same LFS-enabled repository. Filing this from the observed pattern rather than a controlled repro; a maintainer with access to the actual worktree-creation code path should be able to spot the exact wrong-path construction quickly from the "resolves right once core.hookspath is set, wrong before" clue.