Feature: detect and clean stale-PID worktree lock files at session startup
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:
- Enumerate all paths listed in
.git/worktrees/*/locked. - For each lock file, read the PID embedded in the lock reason string (Claude Code writes
something like "claude-code PID <pid>").
- Check whether that PID is still alive (
kill -0 <pid>/os.kill(pid, 0)on macOS/Linux,
OpenProcess on Windows).
- If the process is dead, delete the lock file (equivalent to
git worktree unlock). - 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
| Scenario | Before | After |
|---|---|---|
| Session ends normally | Lock removed on exit (already works) | Same |
| Session killed (SIGKILL / OOM) | Lock persists; next session errors | Lock removed at next session start |
| Another session holds the lock | — | Lock 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 needOpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, pid) in place of kill -0.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗