Repository navigation
Remove locked worktrees, including ones whose directory is gone - #322
Merged
frenchie4111 merged 1 commit intoOct 11, 2026
Merged
Conversation
Claude Code's worktree isolation locks the worktrees it creates in its scratchpad. Once that scratchpad is cleaned up, git keeps a locked entry whose directory is missing. git doesn't flag it prunable and `git worktree prune` skips it, so Ness showed it as a normal worktree and deleting it failed with "cannot remove a locked working tree". - Parse `locked [reason]` from `git worktree list --porcelain`. A locked entry whose directory is missing is reported as prunable, so it gets the Stale badge and no tabs are spawned against it. - pruneWorktrees unlocks missing-directory locked entries before pruning. Locked entries whose directory still exists are left alone. - removeWorktree passes --force twice when the directory is gone. - A locked worktree whose directory still exists asks for confirmation (and shows the lock reason) before it's force removed. The deletion FSM refuses to remove it without that override, and unlocks it before trashing so the prune after the rename doesn't skip it.
frenchie4111
self-requested a review
October 11, 2026 16:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Deleting a worktree from the sidebar failed with:
Claude Code's worktree isolation (EnterWorktree / Agent
isolation: "worktree") creates worktrees in its tmp scratchpad and runsgit worktree lockon them. When that scratchpad gets cleaned up, git is left with a locked entry whose directory is missing.git worktree list --porcelainreportslockedbut neverprunable, andgit worktree pruneskips locked entries. As a result, Ness showed the entry as a healthy worktree, Delete failed, and Stale/prune recovery couldn't clear it either.Fix
parseWorktreeListPorcelainrecordslocked/lockedReason. A locked entry whose directory is missing is also markedprunable, so it gets the Stale badge and PanesFSM doesn't spawn tabs against a missing cwd.pruneWorktrees(the Stale recovery action) unlocks missing-directory locked entries before runninggit worktree prune.removeWorktreepasses--force --forcewhen the directory is gone.window.confirm(the same pattern as the dirty-worktree confirm) that includes the lock reason. On confirm the request carriesoverrideLock, and the deletion FSM unlocks the worktree before teardown and the trash rename. Without the override, the FSM refuses before teardown and gives a clear error. This also fixes a latent bug: the fast path used tomva locked worktree into the trash, and thengit worktree pruneskipped the entry and left a ghost behind.--forcebehavior.Tests
locked [reason], covering both the present-directory and missing-directory cases.worktree-locked.integration.test.ts) covering list, remove, and prune for locked-present and locked-missing entries.npm run typecheck,npx electron-vite build, andnpx vitest runall pass exceptworktree-watcher.integration.test.ts > fires again on commit in a LINKED worktree. That test also fails locally on the base commit. It doesn't touch this code (fs.watch timing).I couldn't run the app locally (the node-pty native rebuild fails on this machine's toolchain), so the confirm dialog hasn't been clicked through in a running app.
Opened on behalf of @blindpirate by Claude via Ness.