From 32fa11829a5a5cf4826f6fd5e3e93b0901a36ec0 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Thu, 13 Aug 2026 19:38:42 -0700 Subject: [PATCH] fix(ssh): make the partition hoist consume its source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A migration that leaves its source on disk runs again every launch, and its hoist condition is 'local has no such tab' — which is exactly the state after the user closes one. Closed panes came back on the next launch. Deleting the partition once hoisted also completes the consolidation: local is now the single plane for SSH pane membership, which is what the reattach binder and the lease arbitration were already assuming. Mutation-proofed: leaving the source behind reddens the new no-resurrection test and nothing else. --- src/main/persistence.ts | 9 +++++++ .../ssh-partition-upgrade-tab-loss.test.ts | 24 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/main/persistence.ts b/src/main/persistence.ts index eba5a989068..cbe99c70171 100644 --- a/src/main/persistence.ts +++ b/src/main/persistence.ts @@ -2913,6 +2913,15 @@ export function hoistSshPartitionsIntoLocalSession( local.activeTabIdByWorktree = { ...local.activeTabIdByWorktree, [worktreeId]: tabId } changed = true } + // Consume the source. A migration that leaves it behind runs again every launch and + // resurrects panes the user has since closed, because "local has no such tab" is exactly + // what this function treats as "needs hoisting". + if (state.workspaceSessionsByHostId?.[hostId]) { + const remaining = { ...state.workspaceSessionsByHostId } + delete remaining[hostId] + state.workspaceSessionsByHostId = remaining + changed = true + } } return changed } diff --git a/src/main/ssh-partition-upgrade-tab-loss.test.ts b/src/main/ssh-partition-upgrade-tab-loss.test.ts index 73a382798f3..64feef17978 100644 --- a/src/main/ssh-partition-upgrade-tab-loss.test.ts +++ b/src/main/ssh-partition-upgrade-tab-loss.test.ts @@ -157,4 +157,28 @@ describe('an SSH pane whose membership was persisted into the ssh partition', () expect(changed, 'the function reported no change').toBe(true) expect(state.workspaceSession?.tabsByWorktree?.[WORKTREE] ?? []).toHaveLength(1) }) + + // A migration that leaves its source behind runs again on the next launch and re-adds panes the + // user has since closed, because "local has no such tab" is precisely its hoist condition. + it('does not resurrect a tab the user closed after upgrading', async () => { + const before = await createStore() + before.setWorkspaceSession(paneSession() as never, SSH_PARTITION) + before.flushOrThrow() + + // The upgrade launch hoists the pane. + const upgraded = await reopenStore() + expect(upgraded.getWorkspaceSession().tabsByWorktree?.[WORKTREE] ?? []).toHaveLength(1) + + // The user closes it, and that sticks. + upgraded.setWorkspaceSession( + { ...upgraded.getWorkspaceSession(), tabsByWorktree: { [WORKTREE]: [] } } as never + ) + upgraded.flushOrThrow() + + const relaunched = await reopenStore() + expect( + relaunched.getWorkspaceSession().tabsByWorktree?.[WORKTREE] ?? [], + 'the migration re-added a tab the user had closed' + ).toHaveLength(0) + }) })