Worktree provisioning writes Git LFS hooks to a literal dev/null/ directory instead of .git/hooks/

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 28, 2026

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 --list in the affected worktree shows core.hookspath already 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 env run fresh inside the affected worktree resolves LocalGitDir=D:\wkspaces\Reveal-Platform\.git\worktrees\relaxed-keller-24b3f9 and LocalGitStorageDir=D:\wkspaces\Reveal-Platform\.git correctly — 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 — .git pointer 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's post-checkout, post-commit, and pre-push hooks installed (dated to original repo setup, well before any worktree existed) — but is missing post-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:

  1. Skip Git LFS hook (re-)installation entirely when provisioning a new linked worktree — hooks are already shared automatically via the common git dir / core.hookspath once the worktree exists, so a fresh per-worktree LFS install is redundant if the main repository already has LFS hooks; or
  2. 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 (and core.hookspath) is fully established — e.g. via git rev-parse --git-common-dir or by reading back core.hookspath — rather than a value that can resolve to a bare relative dev/null when 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.

View original on GitHub ↗