From 9dd94b8d8feb81c4a37ec57728018834ba3f389e Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 27 Jun 2026 23:31:33 -0700 Subject: [PATCH] fix: sweep completed Pi agent row when its terminal tab is closed A 'done' Pi agent entry lingers in agentStatusByPaneKey after its terminal tab is closed: its paneKey's tabId no longer matches any open tab, so dropAgentStatusByTabPrefix never sweeps it. The sidebar selector getLiveEntriesByWorktree used an unconditional fallback (tabIdToWorktreeId.get(parsed.tabId) ?? entry.worktreeId), added in #4371 to surface early child-agent rows before their tab reaches the renderer. That fallback re-attributed the orphaned 'done' entry to the worktree, so buildWorktreeAgentRows rendered it as a stale live 'Done' row forever (Codex/Claude tear down cleanly because their pane/tab keying matches the closed tab). Restrict the worktree-attribution fallback to non-'done' entries so completed rows whose tab is gone are dropped, while still surfacing active child rows whose tab has not yet reached the renderer. Supersedes #5914 (adopted and re-verified). Fixes #5913 Co-authored-by: Alberto Cuervo --- .../worktree-agent-row-selectors.test.ts | 21 +++++++++++++++++++ .../sidebar/worktree-agent-row-selectors.ts | 4 +++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/sidebar/worktree-agent-row-selectors.test.ts b/src/renderer/src/components/sidebar/worktree-agent-row-selectors.test.ts index c3d33e7f08e..a40b6840038 100644 --- a/src/renderer/src/components/sidebar/worktree-agent-row-selectors.test.ts +++ b/src/renderer/src/components/sidebar/worktree-agent-row-selectors.test.ts @@ -150,6 +150,27 @@ describe('selectLiveAgentStatusEntriesForWorktree', () => { expect(selectLiveAgentStatusEntriesForWorktree(state, 'wt-1')).toEqual([childEntry]) }) + + it('does not use worktree attribution for a completed row whose tab is gone', () => { + const closedEntry = makeEntry(PANE_KEY_1, 1000, { + state: 'done', + worktreeId: 'wt-1', + tabId: 'tab-1', + agentType: 'pi' + }) + const state = { + tabsByWorktree: { + 'wt-1': [makeTab('tab-live')] + }, + agentStatusByPaneKey: { + [PANE_KEY_1]: closedEntry + }, + migrationUnsupportedByPtyId: {}, + retainedAgentsByPaneKey: {} + } + + expect(selectLiveAgentStatusEntriesForWorktree(state, 'wt-1')).toEqual([]) + }) }) describe('selectRuntimeAgentOrchestrationForWorktree', () => { diff --git a/src/renderer/src/components/sidebar/worktree-agent-row-selectors.ts b/src/renderer/src/components/sidebar/worktree-agent-row-selectors.ts index 8fe9290ba9f..567454f45cc 100644 --- a/src/renderer/src/components/sidebar/worktree-agent-row-selectors.ts +++ b/src/renderer/src/components/sidebar/worktree-agent-row-selectors.ts @@ -97,7 +97,9 @@ function getLiveEntriesByWorktree(state: WorktreeAgentRowsState): Map