[Bug] Background session worktree locks not released when session ends, blocking ctrl+x removal
Summary
In the background-agents ("Claude Agents") view, pressing ctrl+x to remove a finished session's worktree fails for every session with:
<name> — not deleted · worktree is locked — in use by another live session, or locked by hand
The worktrees pile up and can only be removed manually via git worktree unlock && git worktree remove.
Version: 2.1.216 (macOS, Darwin 25.5.0) — i.e. past the v2.1.210 killed-session lock fix, but still broken.
This is the alive-daemon-pid counterpart to the now-closed #51643. That issue handled the dead-PID stale-lock case (SIGKILLed session leaves a lock whose pid is gone). This one is the opposite: the lock's pid is alive, but it's a long-lived claude bg-spare daemon rather than the session — so the sweep's liveness check never releases it.
Root cause (verified on disk)
Each managed worktree's git lock (.git/worktrees/<name>/locked) records the pid of a claude bg-spare daemon-pool process, not the interactive session that owned the worktree:
$ for d in .git/worktrees/*/; do [ -f "$d/locked" ] && echo "$(basename "$d"): $(cat "$d/locked")"; done
cycle-35+dimension-upsert… : claude session cycle-35/dimension-upsert… (pid 22302 …)
cycle-35+gama-chat-entity-card… : claude session cycle-35/gama-chat-entity-card… (pid 22303 …)
cycle-35+grant-data-architect… : claude session cycle-35/grant-data-architect… (pid 40206 …)
$ ps -o command= -p 22302
claude bg-spare --bg-spare /tmp/cc-daemon-501/…/spare/….claim.sock # ALIVE — a spare daemon, not the session
The auto-release sweep releases a lock only once the locking process has exited. But the recorded pid is a long-lived, recycled spare daemon that outlives the session, so the liveness check is essentially always false and the lock is never reaped. Because every background session's lock is attributed to such a daemon, ctrl+x is blocked for all of them — not just one.
(Separately, a genuinely dead pid — 22303 above — also still had a lingering lock, i.e. the periodic sweep hadn't reaped it yet either.)
Steps to reproduce
- Run several tasks as background agents (each gets an isolated worktree under
.claude/worktrees/). - Let the sessions finish.
- Open the Agents view and press ctrl+x on a finished session to remove its worktree.
Expected
- When a background session ends, its worktree lock is released even though the
bg-sparedaemon that created it is still alive — the lock should be tied to session lifetime, not the daemon pid. - ctrl+x can remove a worktree whose owning session is no longer running.
Actual
- Every finished session's worktree is reported
locked — in use by another live session, or locked by handand is not deleted. - ctrl+x offers no force affordance, so recovery is manual per-worktree:
``bash``
git worktree unlock <path> && git worktree remove <path> --force
Impact
- Worktrees accumulate (I currently have ~15), each carrying a multi-GB per-worktree dev Docker image/volumes that only get reclaimed if the removal hook runs.
- The Agents view becomes unusable for cleanup; users must drop to the CLI for every worktree.
Suggested fix
Attribute the worktree lock to the session's identity/lifetime rather than the bg-spare daemon pid, and/or teach the sweep + ctrl+x path to recognize a lock whose owning session (not daemon) has ended as releasable. Extending #51643's dead-pid reaping to also cover "pid alive but not the owning session" would close this gap.
Environment
- Claude Code 2.1.216
- macOS (Darwin 25.5.0)
- Background-agents / Agents view, per-task isolated git worktrees under
.claude/worktrees/
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗