What
Claude Code now automatically detects and releases worktree locks that were left behind by processes that have since exited, rather than leaving them to block future operations.
Details
- On each sweep, the worktree registry is scanned for locks whose lock-reason string encodes a PID. Only reasons matching the pattern
^claude (?:agent|session) .{1,255} \(pid (\d{1,10})(?: start .{1,255})?\)$(and no longer than 512 characters) are eligible; lock reasons that don't follow this format are skipped entirely. - If
process.kill(pid, 0)returnsESRCH(no such process), the lock is treated as stale and released. Any other result (process exists, or PID ≤ 1 / non-integer) is treated as live and left alone. - The per-sweep cap is 50 releases: once 50 stale locks have been released in one sweep, the remainder are logged and deferred to a later sweep with the message
"releaseStaleClaudeWorktreeLocks: per-sweep cap of 50 reached; remaining stale locks will be reconciled on later sweeps". - Worktrees with no
.gitfile at their path are also skipped, preventing cleanup of already-removed trees. - The registry is re-read at release time (double-checked before each individual unlock) to prevent TOCTOU races where another process might have claimed the lock between the scan and the release.
- The current session's own worktree is always excluded from cleanup (matched by resolved real path).
- When at least one lock is released, a
tengu_worktree_stale_lock_releasedtelemetry event is emitted with{ attempted: N }, and a debug log reports the count:"releaseStaleClaudeWorktreeLocks: attempted release of N stale liveness lock(s)".
Evidence
Cleanup function (search for "releaseStaleClaudeWorktreeLocks: attempted release of") and PID check logic (search for "stale liveness lock(s) in"); cap constant ($zc = 50); lock-reason regex (Mtt)
Strings lifted out of the shipped bundle, so the claim above can be checked against them.