Archiving a desktop session never deletes its worktree when the PR was squash-merged
Environment
- Claude Code 2.1.260, desktop app 1.46388.1 (macOS, Code tab)
- Worktree root configured outside the repo; also reproduces with the default in-repo location
Expected
Per the docs, archiving a session removes its worktree ("To remove a worktree when you're done, hover over the session in the sidebar and click the archive icon").
Actual
For repos where PRs are squash-merged (our org default, and GitHub's most common merge strategy), the worktree survives archiving in practically every case. Over weeks this accumulated ~25 leftover worktrees / ~13 GB (each carries its own node_modules and build output).
Root cause (from inspecting local state)
Cleanup appears to run a safety check equivalent to "branch has commits not on the default branch". Squash-merging rewrites history, so the session branch always looks unmerged by commit ancestry, and the check keeps the worktree, even though:
- the PR is merged (the app itself shows prState MERGED on the session), and
- the branch's upstream is deleted, i.e.
git branch -vshows[gone]aftergit fetch --prune.
Example from git-worktrees.json + on disk: an archived session's worktree remained with its branch at 2 commits "ahead" of main that are all contained in a squash-merge commit. Worktrees whose branches happened to be ancestry-merged were correctly deleted, confirming the cleanup path itself works.
Consequences
- The documented archive behavior effectively never fires for squash-merge workflows.
- The untracked-dir GC does not help either: leftover worktrees stay leased/protected and get recycled into new sessions, which refreshes their protection indefinitely.
- New sessions silently reuse the stale directories (fine per se, but it masks the leak and the sidebar briefly shows a different worktree name than the one actually leased, which is confusing).
Suggested fix
When deciding whether a worktree is safe to delete, treat the branch as merged if any of:
- The session's tracked PR state is MERGED (the app already has this signal), or
- The branch's upstream is gone after a pruning fetch (the standard squash-merge heuristic), or
- Commit ancestry says merged (current check).
Uncommitted/untracked changes should of course still block deletion.
Repro
- In a repo whose PRs are squash-merged, start a desktop session in a worktree, commit, push, open a PR.
- Squash-merge the PR (remote branch deleted).
- Archive the session.
- The worktree directory and local branch remain;
git log main..<branch>shows the squashed commits,git branch -vshows[gone].