fix(ssh): prune the hoist ledgers by relevance, not recency

Finding B. Capping to the newest 512 evicted exactly the entries a long-lived
profile needs most: a ledger entry's only job is to stop a tab the user closed
being folded back in, and a re-hoisted id returns that tab to the plane the tab
bar renders from — on screen, not merely in state.

Now drops ids no ssh partition mentions any more. That bounds growth by what the
host actually offers rather than by an arbitrary count, and it can never evict an
entry that is still load-bearing, because an entry is only dropped once the tab
it guards is gone from the partition too. MAX_HOISTED_TAB_IDS is removed.

Mutation-proofed: restoring the recency cap reddens the new 600-tab eviction test
alone. 512 tests across the persistence suites, E2E still green.
This commit is contained in:
Neil
2026-08-13 22:39:53 -07:00
parent 50be0e673a
commit 330ce992a8
2 changed files with 65 additions and 13 deletions
+26 -13
View File
@@ -2849,11 +2849,6 @@ export type PtyBindingSourceExpectation = {
incarnationId?: string
}
/** Caps the hoist ledger. A live tab is protected by the local-session check, not by this cap, so
* eviction is safe for open tabs; the cost of evicting an id is that its tab could be re-hoisted
* once if it is later closed while the partition still lists it. */
const MAX_HOISTED_TAB_IDS = 512
/** Why a `mayCreate: false` binding write was refused. `noTab` means the pane has no durable tab,
* so publishing it anywhere would be publishing something that does not exist. */
export type PtyBindingRefusalReason = 'noTab' | 'noMembership'
@@ -3021,16 +3016,34 @@ export function hoistSshPartitionsIntoLocalSession(
}
}
if (changed) {
// Bounded, newest-last. A tab id can be reused by the CLI after its tab closes, and an
// unbounded list would both grow for the life of the profile and strand such a pane forever.
// KNOWN COST, and it is worse than it reads: an evicted id can be re-hoisted once, and since
// the unified ledger gates the plane the tab bar renders from, that means a long-closed tab
// reappearing ON SCREEN. Needs >512 hoisted tabs. Tracked, not fixed here.
const bounded = [...alreadyHoisted].slice(-MAX_HOISTED_TAB_IDS)
// Pruned by RELEVANCE, not recency. A ledger entry only has a job while the partition still
// offers that tab: it is what stops a tab the user closed being folded back in. Slicing to the
// newest N evicted exactly the entries a long-lived profile needs most, and a re-hoisted id
// returns a closed tab to the plane the tab bar renders from — on screen. Dropping ids no
// partition mentions any more bounds growth by what the host actually offers, and can never
// evict an entry that is still load-bearing.
const offeredTabIds = new Set<string>()
for (const [hostId, hostSession] of Object.entries(partitions)) {
if (!hostId.startsWith('ssh:') || !hostSession) {
continue
}
for (const tabs of Object.values(hostSession.tabsByWorktree ?? {})) {
for (const tab of tabs ?? []) {
offeredTabIds.add(tab.id)
}
}
for (const tabs of Object.values(hostSession.unifiedTabs ?? {})) {
for (const tab of tabs ?? []) {
offeredTabIds.add(tab.id)
}
}
}
state.settings = {
...state.settings,
sshHoistedTabIds: bounded,
sshHoistedUnifiedTabIds: [...alreadyHoistedUnified].slice(-MAX_HOISTED_TAB_IDS)
sshHoistedTabIds: [...alreadyHoisted].filter((tabId) => offeredTabIds.has(tabId)),
sshHoistedUnifiedTabIds: [...alreadyHoistedUnified].filter((tabId) =>
offeredTabIds.has(tabId)
)
}
}
return changed
@@ -592,4 +592,43 @@ describe('an SSH pane whose membership was persisted into the ssh partition', ()
'a tab the user closed came back in the tab bar'
).toHaveLength(0)
})
// The ledger is what stops a closed tab being folded back in, so an entry must survive exactly
// as long as the partition still offers that tab. Capping by recency evicted the entries a
// long-lived profile needs most, and a re-hoisted id returns a closed tab to the plane the tab
// bar renders from — on screen.
it('keeps ledger entries for every tab the partition still offers, however many there are', async () => {
const before = await createStore()
const many = paneSession()
const ids = Array.from({ length: 600 }, (_, index) => `tab-${index}`)
many.tabsByWorktree[WORKTREE] = ids.map((id) => ({
...many.tabsByWorktree[WORKTREE]![0]!,
id
}))
many.unifiedTabs = { [WORKTREE]: ids.map((id) => unifiedTab(id)) } as typeof many.unifiedTabs
before.setWorkspaceSession(many as never, SSH_PARTITION)
before.flushOrThrow()
stripHoistMarker()
const upgraded = await reopenStore()
expect(
upgraded.getWorkspaceSession().tabsByWorktree?.[WORKTREE] ?? [],
'the tabs never hoisted, so eviction cannot be observed'
).toHaveLength(ids.length)
// The user closes them all.
upgraded.setWorkspaceSession({
...upgraded.getWorkspaceSession(),
tabsByWorktree: { [WORKTREE]: [] },
unifiedTabs: { [WORKTREE]: [] }
} as never)
upgraded.flushOrThrow()
const relaunched = await reopenStore()
expect(
relaunched.getWorkspaceSession().unifiedTabs?.[WORKTREE] ?? [],
'an evicted ledger entry let a closed tab reappear in the tab bar'
).toHaveLength(0)
})
})