[BUG] Worktree's git index silently emptied to 0 entries mid-session (Windows, CLI 2.1.270) — no git commit involved; later commits then build empty trees that delete the whole repo
[BUG] Worktree's git index silently emptied to 0 entries mid-session (Windows, CLI 2.1.270) — no git commit involved; later commits then build empty trees that delete the whole repo
Summary
In a EnterWorktree-created worktree that had been healthy and committing normally
for hours, .git/worktrees/<name>/index was silently rewritten to a structurally
empty index (65 bytes, entry count 0x00000000) with no git commit, git add,git reset, or any other index-writing git command invoked by me in the window.
The working tree was untouched — all 1202 files remained on disk with correct
content. Only the index was destroyed. Nothing surfaced an error; the worktree
looked usable, and git status simply reported all 918 tracked files as deleted.
Why this is severe, not cosmetic: while the index was in this state, two
subsequent ordinary git commit invocations faithfully committed the empty index,
producing commits whose tree was the empty tree — i.e. commits deleting the
entire repository (918 files, 202,019 deletions). Those commits reached no remote
only by luck: the worktree branch happened to have no upstream, so the post-commit
push failed for unrelated reasons. On a branch with an upstream, a
repository-deleting commit would have been pushed to the shared remote, and no
existing pre-commit hook in our chain flagged it (all reported "Passed").
Environment
| | |
|---|---|
| Claude Code | 2.1.270 (CLI) |
| OS | Windows 11 Pro, 10.0.26200.9445 |
| git | 2.55.0.windows.2 |
| Shell | Git Bash (MSYS2), CLAUDE_CODE_SHELL=C:\Program Files\Git\bin\bash.exe |
| Worktree | created via EnterWorktree, under .claude/worktrees/ |
| Concurrency | 4 concurrent Claude Code sessions on this host, 3 in separate worktrees of the same repo |
| Submodules | none (git submodule status empty, no .gitmodules) |
Evidence: the corrupted index, byte for byte
.git/worktrees/session-name-collision-fix/index went from 113,824 bytes (927
entries, captured healthy at 18:14:26 UTC in an instrumented trace) to 65 bytes,
mtime 18:20:
$ xxd -l 32 .git/worktrees/session-name-collision-fix/index
00000000: 4449 5243 0000 0002 0000 0000 5452 4545 DIRC........TREE
00000010: 0000 0019 0030 2030 0a4b 825d c642 cb6e .....0 0.K.].B.n
DIRCmagic, version 2- entry count
0x00000000— zero entries TREEextension caching4b825dc6...— git's canonical empty-tree SHA
This is a genuinely empty index written to disk, not a stale read or a display
artifact. It was independently re-verified byte-for-byte by a second session
inspecting the same file from the main checkout.
Working tree confirmed intact at the same moment:
$ find . -type f -not -path './.git/*' | wc -l
1202
$ git rev-parse HEAD
c0bc83b1db698a2f5a167ce05d1312aeddb7ea08 # unchanged, no new reflog entry
Reproduction / timeline
I cannot yet give deterministic repro steps — this is the honest state of it. What I
can give is a precise action timeline, where every step between the last
confirmed-healthy index and the corrupted one is accounted for:
18:14:26— instrumentedgit commitrun with a diagnostic pre-commit hook that
dumps state then intentionally exit 1s to abort before any commit object is
built. Index confirmed healthy: 113,824 bytes, 927 files. No commit created.
- Read two local log files (no git commands).
git log --oneline -3,git status --short— read-only, state as expected.- Reverted the diagnostic hook from
.pre-commit-config.yamlvia the Edit tool.
git diff .pre-commit-config.yaml → clean, no output. Index still healthy.
- Attempted a second Edit to
.pre-commit-config.yaml. **This Edit was DENIED by
the auto-mode permission classifier ("Auto-Mode Bypass"). No file write
occurred** (confirmed by the immediately-following clean diff).
git diff .pre-commit-config.yaml, thengit status --short— both read-only.- Step 6's
git statusis the first observation of corruption: 918D, 23
??. Index mtime 18:20, placing the on-disk write between step 4 (verified
healthy) and step 6 (corrupted).
Stated explicitly as correlation, not proven causation: the denied Edit (step 5)
is the only non-read-only action in that window, but I have no direct evidence that
the denial/permission pathway performed the write. An unrelated background process
in the same window is equally consistent with the evidence. I'd rather report this
honestly than overclaim a cause.
Control: sibling worktrees were unaffected
Checked at the same moment, same host, same repo, same hook chain:
| index | size | state |
|---|---|---|
| worktrees/session-name-collision-fix | 65 bytes | corrupted |
| worktrees/claim-lock-race-test | 113,141 bytes | healthy |
| worktrees/cr-claim-hook-enforcement | 113,203 bytes | healthy |
| main checkout .git/index | 114,352 bytes | healthy |
So this is not "all worktrees on this host" — it hit exactly one, while two siblings
kept committing normally throughout.
What I ruled out
- pre-commit /
git commitinternals. An instrumented trace (`GIT_TRACE=1
GIT_TRACE_SETUP=1) captured at 18:14:26 shows everything healthy at hook time:GIT_INDEX_FILE
correctly resolved to the worktree's own index, allGIT_TRACE_SETUP
paths correct (git_dir, git_common_dir, worktree, cwd),git write-tree
and pre-commit's own bare returning a correct, fully-populateddc2b2bf5...`, 23 top-level entries) — not the empty tree. The corruption
tree (
does not happen inside pre-commit's execution window. The empty-tree commits
were the victim of an already-empty index, not its cause.
- #57048 (Mac "empty-index regression") — not this: Mac-only, occurs only at
worktree bootstrap ("does not occur during normal session operation"), and fails
loudly. Mine is Windows, mid-session on a worktree healthy for hours, and silent.
- #92019 (
checkout.workers=8Windows regression) — not this: fails at worktree
creation, and that report states it is "not related to git index emptying."
- Our own statusline. It shells out only to
git rev-parse --git-common-dir, a
read-only query that takes no index lock — unlike the git status --porcelain
pattern #47721 identifies as the common offender.
Suspected area
#47721 documents that "Claude Code runs frequent background git commands for
context", and #11005 / #28546 / #57102 document that this same background
machinery already corrupts index state on Windows — leaving 0-byte.git/worktrees/<name>/index.lock files, the same path family as my corrupted
index. Those reports describe locking failures; this is an actual emptying, which
I could not find reported anywhere.
A plausible (unproven) shape: a background git operation racing with, or interrupted
mid-write against, the worktree index, leaving a truncated/empty index rather than
failing cleanly. The concurrency here (4 sessions, 3 worktrees, one repo) would make
such a race far more likely to be hit.
Related but distinct
Searched open/closed issues before filing; found nothing matching this signature.
Nearest neighbours, all materially different:
- #88862 — concurrent sessions sharing one git clone on Windows, with measured
collisions. Same environmental shape as mine (multi-session, one repo, Windows) but
reports collisions/contention, not an emptied index.
- #77268 — worktree recycling destroying sibling worktrees. Destructive and
worktree-related, but it removes worktrees; mine leaves the worktree and working
tree fully intact and destroys only the index.
- #11005 / #28546 / #57102 — 0-byte stale
index.lockfrom background git
operations, including in .git/worktrees/<name>/. Same path family and, I suspect,
the same background machinery — but those are locking failures, not the index
itself being rewritten empty.
- #57048 / #92019 — both fail at worktree creation, loudly. See "What I ruled
out" above.
Suggested mitigations
- **Use
GIT_OPTIONAL_LOCKS=0/--no-optional-locksfor all internal read-only
git operations** (already requested in #47721) — read-only context-gathering
should never be able to write or lock the index.
- Write the index atomically (write temp + rename) so an interrupted background
operation can never leave a truncated/empty index in place.
- Sanity-check before committing an empty tree. A commit whose tree is
4b825dc6... while the working tree is non-empty is essentially never intended.
Even a warning would have prevented two repo-deleting commits here.
Impact
No data was lost in my case — files stayed on disk, and git reset HEAD (mixed)
fully rebuilds the index from HEAD. But the failure is silent, and the resulting
empty-tree commits are pushable. On a tracked branch this would have put a commit
deleting an entire repository onto a shared remote, with every pre-commit hook
reporting success.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗