[BUG] Stale .git/index.lock left behind after Bash tool git commands on Windows

Status Open
Maintainer reply None cached
Activity 13 comments · opened Feb 25, 2026

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

  1. Open Claude Code on Windows
  2. Let CC run any git command via Bash tool (e.g., git status, git add, git commit)
  3. Immediately switch to another tool (Rider, VS Code, or another terminal) and run a git command
  4. Observe index.lock error

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)

View original on GitHub ↗

13 Comments

dfaivre-pcs · 6 months ago

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

  • Claude Code: 2.1.58
  • Platform: WSL2 (Linux 6.6.87.2-microsoft-standard-WSL2)
  • Git: worktree setup (main repo + multiple linked worktrees)

Repro

  1. Open Claude Code in a git worktree directory
  2. Ask CC to run a git command that touches the index (e.g., git reset --hard <ref>)
  3. The command fails with Unable to create '.git/worktrees/<name>/index.lock': File exists
  4. fuser confirms no process holds the lock — it's stale
  5. Remove the lock manually, retry — same failure. CC's background polling re-creates the lock between rm and the git command in the same && chain
  6. Only works if rm and the git command run in separate Bash tool invocations with enough time gap that CC's polling cycle isn't active

Key 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 --porcelain polling 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.

dfaivre-pcs · 6 months ago

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 :(

damusix · 6 months ago

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.

dfaivre-pcs · 6 months ago

Yeah, it also loves to blame it on some rogue "VS Code extension" running somewhere. So frustrating.

kiriz · 5 months ago

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.

Error: Exit code 128
     fatal: Unable to create '<path>/.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.

     fatal: Unable to create '<path>/.git/index.lock': File exists.
HermannBjorgvin · 5 months ago

Also happening here on Ubuntu 24.04

Infuriating

valentinpezon-primo · 5 months ago

Happens on macos, very very painfull

HermannBjorgvin · 5 months ago

Fixed on my end by updating the statusline to use git status --porcelain

Ojisama · 5 months ago
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-locks and the problem was gone.

 47 -    dirty=$(git -C "$cwd" status --porcelain 2>/dev/null)                                                                                                                                
 47 +    dirty=$(git -C "$cwd" --no-optional-locks status --porcelain 2>/dev/null)  
yurukusa · 5 months ago

A PostToolUse hook can automatically clean up stale lock files after git commands:

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
[ -z "$COMMAND" ] && exit 0
echo "$COMMAND" | grep -qE '\bgit\b' || exit 0
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) || exit 0
LOCK="$GIT_DIR/index.lock"
[ -f "$LOCK" ] || exit 0
if ! pgrep -x git > /dev/null 2>&1; then
    rm -f "$LOCK" 2>/dev/null
    echo "INFO: Removed stale .git/index.lock" >&2
fi
exit 0

Settings:

{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Bash",
      "hooks": [{"type": "command", "command": "bash ~/.claude/hooks/git-index-lock-cleanup.sh"}]
    }]
  }
}

This runs after every Bash git command, checks if .git/index.lock exists with no running git process, and removes it. Won't interfere with legitimate locks from concurrent git operations.

pslinn · 4 months ago

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 FileSystemWatcher on .git/worktrees/<name>/ to monitor index.lock creation/deletion with no git commands running:

Detected 6 lock events in 60 seconds:
  18:56:16.757
  18:56:26.797 (gap: 10.0s)
  18:56:36.776 (gap: 10.0s)
  18:56:46.762 (gap: 10.0s)
  18:56:56.758 (gap: 10.0s)
  18:57:06.761 (gap: 10.0s)

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 hits

Only the worktree for the active Claude Code session is polled.

Environment

  • Windows 10 Pro 10.0.19045
  • Claude Desktop v1.1617.0 (Electron)
  • 46 git worktrees sharing one .git directory
  • No custom statusline configured in settings
  • Git 2.47.1.windows.2

Workaround

Built a bash retry wrapper that detects index.lock errors, removes the stale lock via PowerShell (bash rm often 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 --porcelain instead of git status --porcelain.

alexisLefebvre · 2 months ago

Thanks for the script yurukusa but I still had issues.

I restored my workaround in ~/.claude/CLAUDE.md:

## Git (local)

- Prefix git commands that write to the local `.git/` directory with `sleep 0.1 && ` to avoid
  `.git/index.lock` contention. Claude MUST add this prefix ONLY to such commands.
  If the write fails on `.git/index.lock`, retry once after 0.5s.
  - Needs prefix: `git commit`, `git add`, `git push`, `git checkout`, `git fetch`, `git rebase`, 
    `git reset`, `gh stack init`, `gh stack submit`, `gh pr checkout`
  - No prefix: read-only git/gh (`git status`, `git log`, `git diff`, `gh api`, `gh pr view`, `gh`), 
    GitHub API writes that don't touch local `.git/` (`gh pr create`, `gh pr edit`, `gh pr comment`,
    `gh pr merge`), any non-git command (`grep`, `find`, `docker`, etc.)

There are workarounds for the workaround because Claude randomly added sleep 0.1 && to other commands. 🫠

alexisLefebvre · 2 months ago
My statusline script was conflicting with my commands. I had a command to check if repo was dirty (uncommitted files). I added --no-optional-locks and the problem was gone. 47 - dirty=$(git -C "$cwd" status --porcelain 2>/dev/null) 47 + dirty=$(git -C "$cwd" --no-optional-locks status --porcelain 2>/dev/null)

Nice, thanks! Claude suggested export GIT_OPTIONAL_LOCKS=0 instead, because it was easier to alter the 5 git calls.