From c497a26788d41286ec447d01c829800b0127ad33 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 15 Aug 2026 00:03:11 -0700 Subject: [PATCH] fix(ssh): stop the reconnect prepaint firing after its mount is spent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regression I introduced with the snapshot-first reconnect paint. The payload path consumes mountFollowsTerminalPark — it clears the flag after the first reattach so a later in-place reconnect on the SAME mount cannot repaint. I replaced the prepaint's read of that mutable flag with a const snapshot of it, so my combined flag stayed true for the life of the mount. A snapshot could then be written on a later reattach, into a terminal that already had live content, and its own isCurrent() guard could no longer go false either. The visible symptom was a tab that came up blank with no prompt and stayed generically titled Terminal N — the title only stays generic when the shell never printed a prompt for Orca to read one from. Every such tab in my session had been through a remount; four tabs created cleanly with Cmd+T were all fine. So the flag is mutable again and is consumed alongside the one it was derived from. Both reasons a mount paints into an empty terminal — a park and a reconnect — are spent by the first reattach, which is what the original code meant. Worth stating plainly: my earlier claim that the snapshot change was non-regressing was tested only against reconnect scenarios. I never exercised creating a tab afterwards, which is exactly where this showed up. --- .../src/components/terminal-pane/pty-connection.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/terminal-pane/pty-connection.ts b/src/renderer/src/components/terminal-pane/pty-connection.ts index 97c072c2d6a..d7fd03ca4bb 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.ts @@ -7973,7 +7973,11 @@ export function connectPanePty( // it is only set when a pending or live SSH pane retry matches this connection AND this tab // generation. const isSshReconnectRemount = Boolean(directSshRetryAttempt) - const paintsIntoEmptyTerminal = mountFollowsTerminalPark || isSshReconnectRemount + // MUTABLE, and consumed with mountFollowsTerminalPark below. A const here is a bug: the payload + // path clears mountFollowsTerminalPark after the first reattach so a later in-place reconnect on + // the SAME mount cannot repaint, and a snapshot written then lands on a terminal that already + // has live content. Reading a frozen copy reintroduced exactly that. + let paintsIntoEmptyTerminal = mountFollowsTerminalPark || isSshReconnectRemount let parkedSshSnapshotPrefetch: { ptyId: string @@ -8230,6 +8234,9 @@ export function connectPanePty( mountFollowsTerminalPark && (connectResult?.isReattach === true || isRemoteRuntimePtyId(ptyId)) mountFollowsTerminalPark = false + // Consumed together: the prepaint's own guard reads this, and both reasons a mount paints into + // an empty terminal are spent by the first reattach. + paintsIntoEmptyTerminal = false // Why: ordinary parking destroys xterm. Rebuild from the authoritative // host snapshot before releasing queued live bytes; null falls back to // the subscribe screen without keeping the old xterm mounted.