Skip to content
Back to skills

Git Auto Maintenance Recurring Worktree Index Lock

ASecurity

Fix a RECURRING `fatal: Unable to create '.../worktrees/<name>/index.lock': File exists` that comes back after you `rm` it — in a busy/multi-worktree repo where your OWN git commands keep spawning `git maintenance run --auto` (→ `git gc --auto` / `git repack` / `git pack-objects`). Use when: (1) a `git checkout`/`add`/`commit` fails with "Another git process seems to be running … index.lock: File exists", (2) deleting the lock works ONCE then it returns on the next git command, (3) `pgrep -fl...

  • 3 stars
  • 0 votes
  • 0 copies
  • 7 views
  • Added September 6, 2026
ai-agentsgoshellbashgit

Works with

  • claude code

Security analysis

A100/100

Scanned September 22, 2026

npx -y skills add wan-huiyan/agent-traffic-control --skill git-auto-maintenance-recurring-worktree-index-lock --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Git Auto Maintenance Recurring Worktree Index Lock?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Git Auto Maintenance Recurring Worktree Index Lock
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/wan-huiyan-git-auto-maintenance-recurring-worktree-index-lock/badge)](https://www.skillsdirectory.com/skills/wan-huiyan-git-auto-maintenance-recurring-worktree-index-lock)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: git-auto-maintenance-recurring-worktree-index-lock
description: |
  Fix a RECURRING `fatal: Unable to create '.../worktrees/<name>/index.lock':
  File exists` that comes back after you `rm` it — in a busy/multi-worktree repo
  where your OWN git commands keep spawning `git maintenance run --auto` (→ `git
  gc --auto` / `git repack` / `git pack-objects`). Use when: (1) a `git
  checkout`/`add`/`commit` fails with "Another git process seems to be running …
  index.lock: File exists", (2) deleting the lock works ONCE then it returns on
  the next git command, (3) `pgrep -fl 'git (gc|maintenance|repack|pack-objects)'`
  shows background maintenance running, (4) the repo has many loose objects from
  heavy fetch/checkout/commit churn (many worktrees, frequent rebases), OR (5) a PARALLEL
  session's commit was killed mid-flight and left a stale 0-byte lock blocking all sessions in
  the shared worktree. Includes the cause-agnostic forensic test for whether a lock is SAFE to
  remove (lsof-unheld + 0-byte + old mtime). NOT for: stale lock from a crashed async POST-COMMIT
  hook (see `worktree-index-corrupt-async-post-commit-hook` /
  `git-rebase-stalls-async-post-commit-hook`), nor "branch checked out elsewhere" merge errors.
disable-model-invocation: true
author: Claude Code
version: 1.2.0
date: 2026-06-23
---

# Recurring worktree `index.lock` from git auto-maintenance

## Problem
In a repo with many worktrees and heavy object churn, git's **automatic
maintenance** (`maintenance.auto` / `gc.auto`, enabled by default) fires at the
*start* of routine commands once loose-object thresholds are hit. The spawned
`git gc --auto` / `git repack` / `git pack-objects` run concurrently with your
`add`/`commit`/`checkout`, and the contention leaves a **stale
`.git/worktrees/<name>/index.lock`**. You `rm` it, the command works, then the
NEXT git command spawns maintenance again and the lock returns — an infuriating
loop that looks like "another git process is running" when the other process is
git maintaining itself on your behalf.

## Context / Trigger Conditions
- `fatal: Unable to create '.../worktrees/<name>/index.lock': File exists` /
  "Another git process seems to be running … remove the file manually to continue"
- Removing the lock fixes it for exactly one command, then it recurs
- `pgrep -fl "git (gc|maintenance|repack|pack-objects)"` shows live maintenance procs
- Repo has many worktrees / frequent fetches / rebases (lots of loose objects)
- It even blocks a plain `git checkout -b` *before any commit* (rules out a
  post-commit hook as the cause — that's the key discriminator vs the
  async-post-commit-hook skills)

## Solution
1. **Confirm the lock is STALE, then clear it once.** Don't blindly `rm` — a genuinely
   in-progress commit would be corrupted. Three cause-agnostic forensics PROVE staleness
   (any cause: auto-maintenance contention, OR a parallel session's killed/crashed commit):
   ```sh
   LOCK=.git/worktrees/<name>/index.lock          # or .git/index.lock in a plain clone
   lsof "$LOCK"                                    # NO output  => no process holds it open
   [ -s "$LOCK" ] && echo NONEMPTY || echo EMPTY   # 0 bytes    => process died before writing the index snapshot
   ls -la --time-style=full-iso "$LOCK"            # mtime minutes old => not an active commit
   pgrep -fl "git (commit|add|write-tree|gc|maintenance|repack|pack-objects)" | grep -v pgrep
   ```
   **`| grep -v pgrep` is not enough on a machine several sessions share.** It drops the
   `pgrep` process; it does not drop the WRAPPER SHELL that carries the pattern, because the
   Bash tool runs every command inside a `/bin/zsh -c '<whole command text>'`. So this line
   can report git work in progress when the only thing it found was itself, or a peer session
   that merely typed the same words — read each hit's argv and discard any beginning
   `/bin/zsh -c`. `waiter-pgrep-matches-its-own-command-line` has the full trap and the
   ownership checks (`lsof -a -p <pid> -d cwd -Fn`) that settle whose process it is.
   `lsof "$LOCK"` is the load-bearing forensic here and cannot self-match; weight it over
   the process search.

   **Unheld (lsof empty) + 0-byte + minutes-old = textbook stale → safe `rm -f "$LOCK"`** (git's
   own error literally says "remove the file manually to continue"). Re-check `lsof`/size
   immediately before the `rm`, then stage-by-explicit-path + commit at once to minimise the
   re-lock window. NOTE the background `gc`/`pack-objects` use their OWN locks (`gc.pid`, pack
   lock), NOT `index.lock` — their mere presence is not a reason to wait.
2. **Stop YOUR commands from spawning maintenance** — pass the flags INLINE on
   every git write command for the rest of the session:
   ```sh
   git -c gc.auto=0 -c maintenance.auto=false add <paths>
   git -c gc.auto=0 -c maintenance.auto=false commit -F - <<'EOF'
   ...
   EOF
   git -c gc.auto=0 -c maintenance.auto=false checkout -b <branch> origin/main
   git -c gc.auto=0 -c maintenance.auto=false push -u origin <branch>
   ```
3. The background `gc`/`repack` on the shared object store use their OWN locks
   (`gc.pid`, objects pack lock) — separate from the worktree `index.lock` — so
   they don't block *reads*; only your index *writes* contend. Disabling
   auto-maintenance on your commands removes the collision.

## Verification
The same `add`/`commit`/`checkout` that was failing now succeeds, and it stays
fixed across subsequent commands (no recurrence) — because none of them spawns a
new maintenance run.

## Gotcha — the flags MUST be inline, not a shell variable
`GIT="git -c gc.auto=0 -c maintenance.auto=false"; $GIT add …` FAILS in zsh with
`command not found: git -c gc.auto=0 …` — zsh tries to exec a binary literally
named "git -c …". Write the flags inline on each invocation (or use a shell
function / array), never a string variable.

## Notes
- This is the "stale lock files — separate pattern" that
  `worktree-index-corrupt-async-post-commit-hook` explicitly defers to. That
  skill is for `fatal: unable to read <sha>` / cache-tree corruption from a
  crashed async **post-commit hook**; THIS skill is for a recurring **lock file**
  from auto-**maintenance**. Same family (worktree + background git), different
  cause + different fix. See also: `async-doc-hook-autodocs-worktree-locks-branch-checkout`.
- You can also disable it repo-wide for the session
  (`git config maintenance.auto false; git config gc.auto 0`) but per-command
  inline flags are safer (don't mutate shared config other worktrees read).
- Don't kill the running `gc`/`repack` — let them finish; they're not the
  blocker once your commands stop spawning new ones.
- **Sibling cause — killed parallel-session commit (not auto-maintenance).** In a worktree
  shared by multiple concurrent Claude sessions, a session whose `git commit` is killed
  mid-operation leaves a **0-byte** `index.lock` (it died before writing the index snapshot)
  that NEVER clears on its own and blocks EVERY session. Same `Unable to create index.lock`
  symptom, different root cause: it does NOT recur after `rm` (it's a one-shot orphan, not the
  maintenance loop). The Step-1 forensic test (lsof-unheld + 0-byte + minutes-old mtime)
  identifies BOTH causes as safe-to-remove; removing it unblocks the parallel sessions too.
  Always stage by explicit path / never `git add -A` in a shared worktree, so a parallel
  session's uncommitted work isn't swept into your commit (or yours into theirs).
  (Confirmed 2026-06-23: a 9-min-old 0-byte lock from a parallel session's killed commit
  blocked all commits; forensically confirmed stale + removed; all sessions resumed.)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.