[BUG] Stale .git/index.lock left behind after Bash tool git commands on Windows
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
After Claude Code runs git commands via the Bash tool on Windows, .git/index.lock files are left behind. This blocks subsequent git operations from other tools (e.g., JetBrains Rider, VS Code, manual git CLI).
This does not happen when running the same git commands manually in the same terminal. It only occurs when CC's Bash tool executes git.
Previous issue #11005 documented this on Linux/macOS and was closed as NOT_PLANNED. This report is specifically about Windows where the problem is more severe due to Windows' stricter file handle semantics.
Error Message
Unable to create 'C:/DATA/git/project/.git/index.lock': File exists.
Another git process seems to be running in this repository, e.g.
an editor opened by 'git commit'. Please make sure all processes
are terminated then try again. If it still fails, a git process
may have crashed in this repository earlier:
remove the file manually to continue.
Steps to Reproduce
- Open Claude Code on Windows
- Let CC run any git command via Bash tool (e.g.,
git status,git add,git commit) - Immediately switch to another tool (Rider, VS Code, or another terminal) and run a git command
- Observe
index.lockerror
What Should Happen?
Git's .git/index.lock should be cleaned up before the Bash tool returns control. On Windows, this likely requires ensuring the child process (and any subprocess handles) are fully terminated and file handles released before the tool result is sent back.
Likely Cause
On Windows, file handles are not released as eagerly as on Unix. When CC's Bash tool terminates or times out a child process, the git process may not have fully released its lock file. Windows keeps the file locked as long as any handle to it exists, even if the process has logically completed.
Workaround
Manually delete the lock file:
del C:\DATA\git\project\.git\index.lock
Environment
- OS: Windows 11 Enterprise 10.0.26200
- Shell: Git Bash
- Claude Code: Latest (CLI)
- Related: #11005 (closed, NOT_PLANNED — focused on Linux/macOS)
13 Comments
Also reproducible on Linux (WSL2) with git worktrees
This is not Windows-specific. Same root cause as the original #11005 — CC's background git status polling races with foreground git commands.
Environment
Repro
git reset --hard <ref>)Unable to create '.git/worktrees/<name>/index.lock': File existsfuserconfirms no process holds the lock — it's stalermand the git command in the same&&chainrmand the git command run in separate Bash tool invocations with enough time gap that CC's polling cycle isn't activeKey detail: worktree lock path
With worktrees, the lock path is
.git/worktrees/<name>/index.lock(not.git/index.lock), so worktree-unaware cleanup logic won't find it.Suggestion
CC's internal
git status --porcelainpolling should use--no-optional-locks(as one commenter on #11005 suggested for statusline scripts). This flag was added to git specifically for monitoring tools that shouldn't contend with interactive commands.Today, it just can't do anything without having to force remove git locks. I wonder if this is a worktree issue? Claude and manually created worktrees are pretty rough these days :(
This has definitely been reported, and it was closed for some weird reason. It's extremely annoying and constantly asking me to delete index.lock...
https://github.com/anthropics/claude-code/issues/11005
I'm on MacOS M4 Tahoe 26.2
Not specific to Windows. It's CC doing something that causes a lock and kills the agentic loop with delete permissions.
Yeah, it also loves to blame it on some rogue "VS Code extension" running somewhere. So frustrating.
This has become common error for me and I have been asking clause to ignore give another try and it happily succeeds. Looks like a race within the bash script called by Claude as I literally don't have any other session open and doing same activities at the same time.
Also happening here on Ubuntu 24.04
Infuriating
Happens on macos, very very painfull
Fixed on my end by updating the statusline to use git status --porcelain
This ^
My statusline script was conflicting with my commands. I had a command to check if repo was dirty (uncommitted files). I added
--no-optional-locksand the problem was gone.A PostToolUse hook can automatically clean up stale lock files after git commands:
Settings:
This runs after every Bash git command, checks if
.git/index.lockexists with no running git process, and removes it. Won't interfere with legitimate locks from concurrent git operations.Additional data: 10-second internal polling confirmed via FileSystemWatcher
I'm seeing this on Windows 10 Pro, Claude Desktop v1.1617.0, with no custom statusline script configured. The polling is coming from Claude Code's internal harness, not a user statusline.
Exact timing evidence
Used a PowerShell
FileSystemWatcheron.git/worktrees/<name>/to monitorindex.lockcreation/deletion with no git commands running:Each lock file exists for ~6ms. The 10-second interval is exact and consistent.
Confirmed: only the active worktree is polled
Monitored four locations simultaneously for 20 seconds:
.git/worktrees/upbeat-rosalind/— 2 hits.git/worktrees/admiring-beaver/— 0 hits.git/worktrees/cool-blackwell/— 0 hits.git/(main repo) — 0 hitsOnly the worktree for the active Claude Code session is polled.
Environment
.gitdirectoryWorkaround
Built a bash retry wrapper that detects index.lock errors, removes the stale lock via PowerShell (bash
rmoften fails on Windows for these transient files), and retries. Works reliably.The proper fix would be for Claude Code's internal polling to use
git --no-optional-locks status --porcelaininstead ofgit status --porcelain.Thanks for the script yurukusa but I still had issues.
I restored my workaround in
~/.claude/CLAUDE.md:There are workarounds for the workaround because Claude randomly added
sleep 0.1 &&to other commands. 🫠Nice, thanks! Claude suggested
export GIT_OPTIONAL_LOCKS=0instead, because it was easier to alter the 5gitcalls.