Repository navigation
Two remaining single-snapshot absence inferences in remote-runtime-pty-transport (siblings of #20917 / #17825) #20923
Description
Found while fixing #20917 (PR #20922); out of scope there because neither produces that report's symptom.
Both are the same class the fix in #20922 closes on the attach path: a client-side inventory snapshot missing a surface is treated as host removal evidence, from a single observation, off listRemoteRuntimeSessionTabsDeduped — which joins whatever session.tabs.list is already in flight for the worktree and can therefore answer with a snapshot taken before the pane's own leaf was published.
Per docs/reference/ssh-execution-boundary.md that collapses unverifiable into exited. The stricter convention is already established in web-session-terminal-orphan-recovery-inventory.ts: an explicit retiredTerminalSurfaces proof, or two authoritative inventories omitting the identity.
1. waitForResubscribeHostSessionHandle retires on one listing, with no sibling gate
src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.ts:862-865
if (request === 'list') {
if (!hasHostSessionTerminalSurface(listed, hostTabId)) {
return { handle: null, inventoryFailed: false }
}hasHostSessionTerminalSurface matches the requested leaf, so this returns null — which the caller turns into retireRemoteTerminalId(-1) at :1802-1807 — both when the tab publishes siblings but not this leaf and when the snapshot carries no surface for the tab at all.
That second case is exactly what #17945 declared insufficient on the attach path, where the same shape now keeps polling inside the bounded window:
a populated surface list missing only this leaf is positive absence, but a list carrying no surface at all for the tab is a client-side snapshot of a host that may still be republishing
The resubscribe twin never got that treatment, and it has no sibling gate at all — so it is strictly weaker than the branch #20922 has just tightened. It also reads the deduped listing (:835), so it inherits the shared-snapshot hazard: a reconnecting pane can retire on an inventory another pane started before the host republished the surface.
Less damaging than finding 2 — retireRemoteTerminalId is a quiet retirement that leaves the tab recoverable from a later snapshot, per its own comment — but it does fire onPtyExit(stalePtyId, -1) and latch terminalEnded, so the pane reports an exit its host never attested.
2. attach() latches the pane in connecting with no banner and no retry
src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.ts:2515-2518
if (!resolved) {
surfaceErrorMessage('Remote terminal was closed.')
return
}resolvePersistedHostPane() returns null for a terminal_not_found from terminal.resolvePane, and the host raises that name for two different situations (orca-runtime-resolve-terminal-pane.ts:19-41): no handle currently bound to the pane key, and a worktree-ownership mismatch. Neither is process-liveness evidence.
The branch is also missing the state handling its sibling in attachHostSessionMirror performs before surfacing the same string (:976-981):
connecting = false
emitRecoveryState()
surfaceErrorMessage('Remote terminal was closed.')attach() sets connecting = true on entry and cancels recovery, and this path clears neither. getRecoveryState() (:460-481) therefore keeps reporting phase connecting — the state #17945's own commit message describes as the dead end:
a silent 'connecting' renders no banner and retryRecovery() refuses to run
So the pane shows an undismissable "Remote terminal was closed." with no Reconnect affordance and no parked retry, which is the shape of #9194 and #15141.
Activity
AmethystLiang commentedon Sep 16, 2026
thanks for reporting. looking into it
LesleyMurfin commentedon Sep 17, 2026
Cross-referencing: Linking PR #21277 and PR #20922 as part of the broader remote tab lifecycle and transport hardening cluster. While #20923 addresses single-snapshot absence inferences in the remote PTY transport layer, PR #21277 hardens the host RPC and client lifecycle against absent selectors and missing tabs to eliminate ghost tab resurrection loops.