Repository navigation
[Bug]: Storage cleanup never removes a worktree that a thread shared with its own subagents #16472
Copy link
Copy link
Open
Labels
bugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
Description
Activity
Note
Grok responding on behalf of Julius.
Triage
Thanks @Info-Cado for the precise report and the numbers from your machine. From the code on
main, this looks like a separate issue from #15146, #14742, and the idle-status change in #15150.What I found
cleanWorktreesgroups every non-deleted thread by resolvedworktreePathand only considers a path owned by exactly one thread (apps/server/src/storageCleanup.ts, thegroup.length === 1filter); the recheck before removal requireslatest.length === 1too. That rule dates from feat(settings): add automatic storage cleanup per machine and project #11598, before worktrees were also stored on child threads.- V2 subagents copy the parent checkout:
makeSubagentChildThreadspreads the parent (soworktreePathandbranchcome along), anddelegate_taskdoes the same viadelegated_task.request. Both setlineage.relationshipToParentto"subagent", and those children are in the active and archived shell snapshots, so a single subagent is enough to keep the path forever. worktreeOnDeletehas the same gap: a still-live child keepsgroups.has(...)true, and deleting the parent doesn't delete the child thread.- The "shared worktrees" exception in the user guide is still a real case:
create_threadscopies the parentworktreePathbut leavesrelationshipToParentnull, and those are independent conversations. Forks and other genuine co-owners likely should keep blocking removal. - Interaction with [Bug]: V2 storage cleanup never frees worktrees for completed/cancelled/interrupted/rolled_back threads #15146: fix(server): free worktrees for terminal thread statuses #15150 only changes
storageCleanupThreadIdle, and a successfully finished turn iscompleted, which that predicate still rejects, so your repro stays masked until [Bug]: V2 storage cleanup never frees worktrees for completed/cancelled/interrupted/rolled_back threads #15146 is addressed. Afailedparent is already idle-eligible and is still dropped here today. On your machine, ignored files and squash-merge ancestry ([Bug]: V2 storage cleanup never frees worktrees for completed/cancelled/interrupted/rolled_back threads #15146 / [Bug]: Merged-worktree cleanup retains worktrees after squash merges #14742) may also be retaining some of those checkouts.
Likely fix area
The grouping in
cleanWorktreesandworktreeOnDelete. One option is to fold only"subagent"children into their parent's ownership, with a couple of constraints:- Provider-native children (
creationSource: "provider") can't take messages (isProviderNativeSubagentThread), whiledelegate_taskchildren (creationSource: "mcp") can.ProviderTurnStartServicerecreates a missing checkout only when a thread has bothworktreePathandbranch, andthread.metadata.updatecan change them independently. So folding could be limited to children that can't take messages or that still share the parent's branch, with any other co-owner keeping the path shared. - Folded children would still need to be idle (a delegated child can still be running or queued after the parent's turn ends), the existing session, terminal, dirty-tree, and ignored-file checks would stay, and the inactivity clock could use the latest activity across the family rather than the parent alone.
A maintainer will decide on the fix direction.
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Oct 6, 2026 - added a commit that references this issue
on Oct 7, 2026
Metadata
Metadata
Assignees
Labels
bugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
Before submitting
Area
apps/server
Steps to reproduce
node_modules/.delegate_task.Expected behavior
The subagent works inside its parent's checkout, so it should count as part of the parent thread. The checkout should be removed under the same rules as a thread without subagents, as long as the parent and all its subagents are idle.
Actual behavior
The worktree is never considered. Every subagent thread is stored with its parent's
worktreePath.cleanWorktreesinapps/server/src/storageCleanup.ts(around lines 215-222) only keeps a worktree path that exactly one thread owns:So a thread that ever used a subagent keeps its worktree forever, whatever its age, merge state or status. The re-check before removal (
latest.length !== 1) has the same rule.The single-owner rule came from V1 (#11598), where subagents were not app threads. In V2 they are app threads with
lineage.relationshipToParent: "subagent", so the rule now also catches a thread's own subagents. The user guide says "shared worktrees ... prevent removal", which reads as sharing between separate threads, not a thread and its own subagents.Note on delegated children:
delegate_taskchildren (creationSource: "mcp") can take messages later. When a turn starts on a thread whose checkout is missing,ProviderTurnStartServicerecreates it from that thread's ownbranch. Some delegated children on my machine havebranch: null, so a fix should only count a child that cannot take messages (provider-native) or that has the parent's branch.Impact
Major degradation or frequent failure
Version or commit
main @ 9bd1d80; T3 Code Nightly 0.0.46-nightly.20261005.2667
Environment
Linux, desktop AppImage. Claude and Codex providers.
Logs or stack traces
On this machine, #15146 (the
completedstatus gate) also blocks these 17 worktrees, so they are hidden twice. Fixing #15146 alone still leaves them, because they never pass the single-owner grouping. This issue is separate from #15146 and from #14742 (squash merges).Workaround
Remove the worktree by hand. The branch stays, and the next turn on the thread recreates the checkout.
Investigated and written by Claude Opus 5.5 through T3 Code's Claude Code harness, on behalf of the reporter.