Worktree provisioning writes the git exclude entry to a literal --git-common-dir/ directory in the MAIN checkout instead of .git/info/exclude
Summary
A directory named literally --git-common-dir appears in the main checkout's
root, containing a single file info/exclude whose contents are.claude/worktrees/.
That is the shape of a path built from git rev-parse --git-common-dir where the
command was never executed and the flag itself was used as the path — i.e. the
intended write to $(git rev-parse --git-common-dir)/info/exclude landed in./--git-common-dir/info/exclude instead.
The intent is clear and correct: add .claude/worktrees/ to the repository's
local git exclude file so Claude Code's worktree directory stays untracked. The
intended write never reached .git/info/exclude, which still has no active
entries.
This looks like a sibling of #90456 ("Worktree provisioning writes Git LFS hooks
to a literal dev/null/ directory instead of .git/hooks/") — same subsystem,
same class of defect (a path literal used before/without resolution during
worktree provisioning), different write and different platform. Filing
separately because the wrong path, the file written, and the destination differ;
a maintainer may well find one root cause behind both.
Difference worth noting from #90456: that one pollutes the newly created
worktree. This one pollutes the main repository checkout.
Environment
- Claude Code 2.0.42, macOS 26.5.2 (Apple Silicon), zsh
- git 2.50.1 (Apple Git-155)
- Repo: a normal (non-bare, non-separate-git-dir, non-worktree) checkout;
.git is a real directory and git rev-parse --git-common-dir returns .git
- No Git LFS involved
- Configured hooks: two
PreToolUseBash guards of my own; neither touches
exclude files
- The repo does use linked worktrees, but at sibling paths (
../.wt-<name>),
created manually — .claude/worktrees/ itself exists but is empty
Evidence
$ find -- './--git-common-dir'
./--git-common-dir
./--git-common-dir/info
./--git-common-dir/info/exclude
$ cat -- './--git-common-dir/info/exclude'
.claude/worktrees/
$ stat -f "created: %SB modified: %Sm" -- './--git-common-dir/info/exclude'
created: Sep 1 18:01:13 2026 modified: Sep 1 18:01:13 2026
$ git rev-parse --git-common-dir
.git
$ cat .git/info/exclude | grep -v '^#' | grep -v '^$'
(no active entries)
It has been created more than once. An earlier session in this repo recorded
the directory as "created today at 16:50"; the copy I removed today was stamped18:01:13. So this is not a single stray keystroke — something recreates it.
It was not created by any logged tool call. I grepped every Claude session
transcript for this project for commands touching info/exclude or invokingrev-parse --git-common-dir. Every hit is either prose discussing the directory
or my own commands from the session that investigated it. No session ever ran a
command that would create it, which points at machinery below the Bash-tool
layer.
Ruled out as the writer:
- The two configured
PreToolUsehooks — neither writes exclude files. - The official
security-guidanceplugin'shooks/gitutil.py, which does use
--git-common-dir but correctly, as an argv element to subprocess.run, and
never writes to info/exclude.
- The
info/excludestrings inside theclaudebinary — those come from the
bundled Rust ignore crate (a reader of exclude files) and from bundled git
man-page text.
- Other repositories: a scan of
~/Projectsfound no other--git-common-dir
directory, so this is not a fleet-wide sweep.
Not reproduced on demand. As in #90456, this is provisioning logic internal
to the CLI rather than something scriptable from a user session, so I am filing
from the observed pattern. Since removing the directory (~20 minutes ago, with
no worktree created in that window) it has not returned, which is consistent
with a trigger that fires on worktree creation.
I want to be explicit that the attribution to worktree provisioning is
inference, not proof: the content (.claude/worktrees/) and the timing are
what point there, plus the structural match to #90456. I have no log of the
write itself.
Impact
Low severity in this repository, but the low severity is luck. The committed.gitignore here already contains .claude/, so .claude/worktrees was ignored
regardless and the failed write cost nothing.
In a repo without such a rule, the same failure means the exclusion silently
does not happen — .claude/worktrees/ would then show up as untracked content,
which is the outcome the write exists to prevent.
Two secondary costs, both observed here:
- Recurring investigation. Four separate sessions in this repo noticed the
stray directory, flagged it as junk, and left it alone rather than deleting
something unexplained. That is the same "is my workspace clean" tax #90456
describes.
- Near-miss on a commit. One session's transcript records it reaching the
index (A --git-common-dir/info/exclude) and nearly being swept into a
commit by a broad git add. A stray directory in the main checkout root is
more exposed to that than one inside a throwaway worktree.
Suggested fix
Resolve the common git dir by executing git rev-parse --git-common-dir and
using its output, then join info/exclude onto that absolute path — and
fail loudly rather than falling back to the flag string when the call fails or
the worktree plumbing is not yet in place.
Worth checking the same code path for other places a flag or sentinel string can
be used where a resolved path is expected; #90456 suggests at least one more
exists.
Finally: if the exclude write fails, it would be better to surface that than to
proceed, since a silent failure leaves the untracked-worktree problem unsolved
in exactly the repos that need it most.