[BUG] Windows: worktree creation leaves a junk dev/null/ directory of git-lfs hooks

Open 💬 1 comment Opened Jun 18, 2026 by steder

Preflight

  • I searched existing issues. This is closely related to the core.hooksPath worktree-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 junk dev/null/ directory of git-lfs hooks behind. Searches for dev/null lfs and worktree git lfs hooks returned 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

  1. On Windows, in a repository with Git LFS enabled.
  2. Create a worktree session (desktop "worktree" checkbox, claude --worktree, EnterWorktree, or Agent(isolation: "worktree")).
  3. cd into the new worktree and run git status → an untracked dev/null/ directory with the four git-lfs hooks is present.

Impact

  • git status is dirty in every worktree, every session (easy to accidentally git 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-repo when the intent is to register filters without placing hooks.
  • Don't use /dev/null as the hooks-path sink on Windows — git-lfs can't write to it portably. Use NUL on 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.hooksPath during worktree creation at all.

Workaround

Add an anchored ignore so it stops polluting status (does not stop creation):

/dev/null/

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗