From b2bc6b9020bb866233f66d394a9ee81731b29ef8 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 11 Jul 2026 03:10:45 -0700 Subject: [PATCH] fix: skip retention suppressor for completed-orphan keys during tab close A completed-orphan key's tab is already gone, so buildRetainedAgentsSyncSnapshot never snapshots it and no live->gone transition ever fires to consume a planted retention suppressor. Planting one leaked a permanent retentionSuppressedPaneKeys entry per swept orphan. Plant suppressors only for prefix-matched keys. Co-authored-by: Orca --- src/renderer/src/store/slices/agent-status-drop.test.ts | 4 +++- src/renderer/src/store/slices/agent-status.ts | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/store/slices/agent-status-drop.test.ts b/src/renderer/src/store/slices/agent-status-drop.test.ts index af9f9eaa7ed..5a4b86838c2 100644 --- a/src/renderer/src/store/slices/agent-status-drop.test.ts +++ b/src/renderer/src/store/slices/agent-status-drop.test.ts @@ -186,7 +186,9 @@ describe('dropAgentStatus + retention suppressor', () => { expect(s.tabsByWorktree['wt-1']?.some((tab) => tab.id === 'tab-closed')).toBe(false) expect(s.agentStatusByPaneKey['tab-closed:0']).toBeUndefined() expect(s.agentStatusByPaneKey['tab-orphan:0']).toBeUndefined() - expect(s.retentionSuppressedPaneKeys['tab-orphan:0']).toBe(true) + // No suppressor for the orphan: its tab is already gone, so retention sync + // never re-surfaces it and a suppressor would leak permanently. + expect(s.retentionSuppressedPaneKeys['tab-orphan:0']).toBeUndefined() expect(s.agentStatusByPaneKey['tab-active-child:0']).toBeDefined() expect(s.agentStatusByPaneKey['tab-live:0']).toBeDefined() expect(s.agentStatusByPaneKey['tab-other-orphan:0']).toBeDefined() diff --git a/src/renderer/src/store/slices/agent-status.ts b/src/renderer/src/store/slices/agent-status.ts index 80be0c22e3a..52809ebaa2c 100644 --- a/src/renderer/src/store/slices/agent-status.ts +++ b/src/renderer/src/store/slices/agent-status.ts @@ -1895,7 +1895,13 @@ export const createAgentStatusSlice: StateCreator !(k in s.retentionSuppressedPaneKeys)) + // + // Skip completed-orphan keys: their tab is already gone, so retention + // sync never snapshots them and no live→gone transition ever fires to + // consume the suppressor — planting one would leak permanently. + const suppressorAdds = liveKeys.filter( + (k) => !completedOrphanKeySet.has(k) && !(k in s.retentionSuppressedPaneKeys) + ) let nextRetentionSuppressedPaneKeys = s.retentionSuppressedPaneKeys if (suppressorAdds.length > 0) { nextRetentionSuppressedPaneKeys = { ...s.retentionSuppressedPaneKeys }