claude-code - 💡(How to fix) Fix Feature: detect and clean stale-PID worktree lock files at session startup [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#51643Fetched 2026-04-22 07:56:45
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Participants
Timeline (top)
labeled ×2

Fix Action

Fix / Workaround

Prior art / local workaround

Code Example

fatal: worktree '<path>' is locked

---

for lock_file in .git/worktrees/*/locked; do
  pid=$(grep -oP 'PID \K[0-9]+' "$lock_file" 2>/dev/null) || continue
  kill -0 "$pid" 2>/dev/null && continue   # still alive — skip
  git worktree unlock "${lock_file%/locked}" 2>/dev/null
  echo "Removed stale worktree lock (PID $pid no longer running)"
done
RAW_BUFFERClick to expand / collapse

Problem

When a Claude Code session ends abnormally (OOM kill, force-quit, power loss, SIGKILL), the .git/worktrees/<name>/locked file that Claude Code writes to claim an active worktree is never cleaned up. On the next session start, git worktree list shows the worktree as locked, and any attempt to prune or reuse it fails:

fatal: worktree '<path>' is locked

This is particularly painful in setups that recycle named worktrees across sessions (e.g. wt-<feature>-<hash>). Users must manually run git worktree unlock <path> or delete the lock file to recover.

Proposal

At session startup, before the workspace is entered, Claude Code should:

  1. Enumerate all paths listed in .git/worktrees/*/locked.
  2. For each lock file, read the PID embedded in the lock reason string (Claude Code writes something like "claude-code PID <pid>").
  3. Check whether that PID is still alive (kill -0 <pid> / os.kill(pid, 0) on macOS/Linux, OpenProcess on Windows).
  4. If the process is dead, delete the lock file (equivalent to git worktree unlock).
  5. Log a one-line notice: Removed stale worktree lock for <path> (PID <pid> no longer running).

No action should be taken when:

  • The PID is still alive (another Claude Code session is legitimately holding the lock).
  • The lock file has no recognisable PID in it (written by another tool; leave it alone).

Why session-start is the right hook

  • It runs once, before any worktree operation, so the cleanup is invisible to the user.
  • It requires no background daemon or OS-level file-watch.
  • It is idempotent: running it on a clean workspace is a no-op.

Prior art / local workaround

We shipped a local self-heal in session_start.py::unlock_dead_worktrees() plus a ~/bin/claude-clean launchd job that fires 60 s after login. It has been reliable across dozens of abnormal-exit scenarios on macOS. The core logic is ~30 lines of Python and maps directly onto the shell equivalent:

for lock_file in .git/worktrees/*/locked; do
  pid=$(grep -oP 'PID \K[0-9]+' "$lock_file" 2>/dev/null) || continue
  kill -0 "$pid" 2>/dev/null && continue   # still alive — skip
  git worktree unlock "${lock_file%/locked}" 2>/dev/null
  echo "Removed stale worktree lock (PID $pid no longer running)"
done

Expected behaviour after fix

ScenarioBeforeAfter
Session ends normallyLock removed on exit (already works)Same
Session killed (SIGKILL / OOM)Lock persists; next session errorsLock removed at next session start
Another session holds the lockLock preserved (PID alive check)
Lock written by 3rd-party tool (no PID)Lock preserved (no PID → skip)

Platform

macOS 14 (Sonoma) / macOS 15 (Sequoia); should be equivalent on Linux. Windows would need OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, pid) in place of kill -0.

extent analysis

TL;DR

Implement a session startup hook to clean up stale worktree locks by checking for dead processes and removing corresponding lock files.

Guidance

  • Enumerate all .git/worktrees/*/locked files and extract the PID from each lock reason string.
  • Use kill -0 <pid> (or equivalent) to check if the process is still alive, and delete the lock file if it's dead.
  • Log a notice when removing a stale lock file, including the path and PID.
  • Ensure the cleanup process is idempotent and runs only once at session startup.

Example

for lock_file in .git/worktrees/*/locked; do
  pid=$(grep -oP 'PID \K[0-9]+' "$lock_file" 2>/dev/null) || continue
  kill -0 "$pid" 2>/dev/null && continue   # still alive — skip
  git worktree unlock "${lock_file%/locked}" 2>/dev/null
  echo "Removed stale worktree lock (PID $pid no longer running)"
done

Notes

This solution assumes that the lock reason string always contains the PID in the format "claude-code PID <pid>". If this format can vary, additional parsing may be necessary. Additionally, the kill -0 command may not work on all platforms, so alternative methods like OpenProcess on Windows should be used when necessary.

Recommendation

Apply the proposed workaround by implementing the session startup hook to clean up stale worktree locks, as it provides a reliable and idempotent solution to the problem.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix Feature: detect and clean stale-PID worktree lock files at session startup [1 participants]