[Bug] Background session worktree locks not released when session ends, blocking ctrl+x removal

Status Open
Reported on v2.1.216
Maintainer reply None cached
Activity 1 comment · opened Jul 21, 2026

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

  1. Run several tasks as background agents (each gets an isolated worktree under .claude/worktrees/).
  2. Let the sessions finish.
  3. 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-spare daemon 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 hand and 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/

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗