[BUG] Windows: worktree creation leaves a junk dev/null/ directory of git-lfs hooks
Preflight
- I searched existing issues. This is closely related to the
core.hooksPathworktree-setup cluster (#66993, #67914, #27474, and closed #60620 / #60957), but those describe the config rewrite / hook-disabling behavior. This report is about a distinct, unreported Windows-only side effect: the worktree-setup step leaves a junkdev/null/directory of git-lfs hooks behind. Searches fordev/null lfsandworktree git lfs hooksreturned nothing. - Single bug, latest version.
Environment
- Claude Code: 2.1.179
- Platform: Windows 11 (10.0.26200), Git Bash available
- git: 2.54.0.windows.1
- git-lfs: 3.7.1 (this matters — see mechanism)
- node: v24.16.0
- Repo: a normal repository with Git LFS enabled (
filter.lfs.*configured)
Summary
On Windows, every Claude Code worktree (the desktop "worktree" option, --worktree, EnterWorktree, or Agent(isolation: "worktree")) is created with a stray dev/null/ directory at the worktree root containing four git-lfs hook files:
dev/null/post-checkout
dev/null/post-commit
dev/null/post-merge
dev/null/pre-push
They are git-lfs's standard hook scripts (the 3.x template that references core.hookspath). They are never tracked, but they clutter git status in every worktree and recur on every new one. Across two local clones I found this in ~45 worktrees — i.e. one per worktree, 100% reproducible.
What's actually happening
This is the Windows manifestation of the same worktree-setup hook-path handling covered by #66993 / #27474. The setup path ends up invoking git-lfs (git lfs install, or a git operation that triggers it) while core.hooksPath resolves to /dev/null — the common "install/refresh LFS filters without placing or firing hooks" idiom, and/or the hooks-disable sink used during setup.
On Linux/macOS, /dev/null is the real null device, so git-lfs writing hooks there is harmlessly discarded. On Windows there is no /dev/null device, and git-lfs 3.x does not translate it — so filepath.Join resolves it to a path relative to the current working directory and git-lfs happily creates ./dev/null/ and writes its four hooks into it. The worktree's persisted core.hooksPath ends up correct (absolute …/.git/hooks), so the bad /dev/null value is only present transiently during setup — but the junk directory it created is left on disk.
Note this is not MSYS/Git-Bash path mangling (MSYS_NO_PATHCONV=1 does not prevent it). It is git-lfs's own Windows path handling, so it occurs even when git is invoked directly.
Minimal deterministic reproduction (no Claude Code required)
On Windows with git-lfs installed, in any repo:
git -c core.hooksPath=/dev/null lfs install --force
ls dev/null
# -> post-checkout post-commit post-merge pre-push (junk, created relative to CWD)
Claude Code reproduction
- On Windows, in a repository with Git LFS enabled.
- Create a worktree session (desktop "worktree" checkbox,
claude --worktree,EnterWorktree, orAgent(isolation: "worktree")). cdinto the new worktree and rungit status→ an untrackeddev/null/directory with the four git-lfs hooks is present.
Impact
git statusis dirty in every worktree, every session (easy to accidentallygit add .it).- Confusing: looks like a path-mangled redirect bug in the user's own tooling; took a full investigation to trace to the harness.
- Cosmetic only (the real hooks are installed correctly elsewhere), but persistent and recurring.
Expected behavior
Worktree setup should not leave a dev/null/ directory on Windows. Options:
- Use
git lfs install --skip-repowhen the intent is to register filters without placing hooks. - Don't use
/dev/nullas the hooks-path sink on Windows — git-lfs can't write to it portably. UseNULon Windows, or a real throwaway/absolute path, or simply omit the install step in the worktree (linked worktrees already share$GIT_COMMON_DIR/hooks). - More broadly, per #27474 / #66993, avoid touching
core.hooksPathduring worktree creation at all.
Workaround
Add an anchored ignore so it stops polluting status (does not stop creation):
/dev/null/This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗