# \[Bug\]: Worktree cleanup never removes a worktree whose branch was renamed, switched, or detached · Issue \#17813 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/issues/17813)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: backlog

Priority: 0

## Description

[Discussion and activity](https://git.cafe/pingdotgg/t3code/issues/17813)

---

### Before submitting

- [x] I searched existing issues and did not find a duplicate.
- [x] I included enough detail to reproduce or investigate the problem.

### Area

apps/server

### Steps to reproduce

Storage cleanup enabled with **Remove after N inactive days** (also applies to the merged and unchanged rules).

**A. Branch renamed or switched inside the worktree**

1. Start a thread in a new worktree and let the first turn finish. The thread records the branch, for example `t3code/fix-thing`.
2. In that worktree (the agent or a terminal), run `git branch -m fix/thing`, or `git switch -c fix/thing`.
3. Commit, leave the worktree clean, close its terminals, and let the thread go idle past the inactivity window.
4. Run cleanup from **Settings → Storage**.

**B. Detached HEAD**

1. Start a thread in a new worktree as above.
2. Detach HEAD in the worktree, for example `git switch --detach origin/main`. A colocated Jujutsu repository does this without the user asking: when jj exports refs and the T3 branch is gone, the worktree's HEAD reflog shows `export from jj` and HEAD is left as a bare commit.
3. Leave the worktree clean and idle past the inactivity window, then run cleanup.

The steps are derived from the source and from the state of the affected worktrees; I have not replayed them on a fresh dev server.

### Expected behavior

The worktree is evaluated against the configured rules. A clean, idle worktree whose commits are all reachable from a branch or remote is removed once it is old enough, and the thread's recorded branch follows a rename or switch made inside its own worktree.

### Actual behavior

The worktree is kept on every sweep with the reason "Repository or branch changed.", and it never becomes eligible.

`cleanWorktrees` compares the checked-out branch with the thread's recorded branch before any rule runs:

https://github.com/pingdotgg/t3code/blob/f7664fdf425555e0f85b74d904cfa4eaf57cf7af/apps/server/src/storageCleanup.ts#L418-L420

Two things make that permanent:

- **The recorded branch is never reconciled for worktree threads.** `thread.branch` is written at launch, by the one-time temporary-branch rename, by the branch picker, and by the git action. The client's mismatch handling (`resolveLocalCheckoutBranchMismatch`) returns `null` whenever the thread has a worktree. After a turn, `RunFinalizationService` sees the mismatch and returns, which also skips that thread's pull request refresh:
  https://github.com/pingdotgg/t3code/blob/f7664fdf425555e0f85b74d904cfa4eaf57cf7af/apps/server/src/orchestration-v2/RunFinalizationService.ts#L105-L107
- **Detached HEAD reports `branch: null`**, which can never equal the recorded branch.

The check is not purely a bug: removal relies on `ProviderTurnStartService` recreating the checkout from `thread.branch` on resume, so removing a worktree that sits on another branch would resume the thread on the wrong one. The gap is that nothing brings the record back in line, so the protection never lifts. The check has no test in `storageCleanup.test.ts` and is not mentioned in `docs/user/project-settings.md`.

Observed on one machine: 10 single-thread worktrees kept this way across two projects. 7 were on a detached HEAD (6 detached by jj export, with the recorded branch no longer existing locally or on the remote; 1 left detached after a rebase). 3 were on a renamed or newly created branch. In all 10, `git rev-list --count HEAD --not --branches --remotes` was 0, so removal would have orphaned no commits, and 8 had no uncommitted changes.

### Impact

Minor bug or occasional failure

### Version or commit

0.0.46-nightly.20261010.2922; source read at main @ f7664fdf42

### Environment

Linux, desktop server. One affected project is a colocated Jujutsu repository.

### Workaround

Remove the worktree manually, or switch it back to the branch the thread recorded if that branch still exists.

### Related

- #17563 added the "Repository or branch changed" reason, which made this visible.
- #15080 (open) rewrites this code path and keeps the same comparison.
- #16380 (open) adds Jujutsu support and touches `storageCleanup.ts`.

A possible direction, for maintainers to judge: record the worktree's current named branch on the thread when a run finishes, and for a detached HEAD allow removal only when every commit reachable from HEAD is also reachable from a branch, remote, or tag. What a resumed thread should do when its recorded branch no longer exists is an open question.

