anthropics / anthropics/claude-code

Archiving a desktop session never deletes its worktree when the PR was squash-merged

Open
#92,061 0 comments 0 reactions 0 assignees View on GitHub
area:desktop bug has repro platform:macos
Dominant language
Python
Stars
145k
Forks
23.1k
PR merge metrics
PR metrics pending

Description

## 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 -v` shows `[gone]` after `git 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:
1. The session's tracked PR state is MERGED (the app already has this signal), or
2. The branch's upstream is gone after a pruning fetch (the standard squash-merge heuristic), or
3. Commit ancestry says merged (current check).

Uncommitted/untracked changes should of course still block deletion.

## Repro
1. In a repo whose PRs are squash-merged, start a desktop session in a worktree, commit, push, open a PR.
2. Squash-merge the PR (remote branch deleted).
3. Archive the session.
4. The worktree directory and local branch remain; `git log main..` shows the squashed commits, `git branch -v` shows `[gone]`.

Contributor guide

No contributing guide indexed for this repository

Research direction

Trace the archive cleanup safety check and its handling of the session's prState MERGED and deleted upstream, using git-worktrees.json and the git branch -v/fetch --prune state from the reproduction. Reproduce a squash-merged session, then verify that archiving removes its worktree while uncommitted or untracked changes still block deletion.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, python
Domain
desktop-dev, devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.