diff --git a/src/main/providers/ssh-pty-restore-required-reattach.test.ts b/src/main/providers/ssh-pty-restore-required-reattach.test.ts index db4c20deab8..abcd779e50b 100644 --- a/src/main/providers/ssh-pty-restore-required-reattach.test.ts +++ b/src/main/providers/ssh-pty-restore-required-reattach.test.ts @@ -303,7 +303,7 @@ describe('SSH PTY reattach when the relay requires source restoration', () => { expect(mux.request.mock.calls.filter((call) => call[0] === 'pty.attach')).toHaveLength(2) }) - it('does not install a second activation when the first cancellation is unverifiable', async () => { + it('keeps the PTY live when the first activation cancellation is unproven', async () => { const mux = createMockMux() const activation = { status: 'pending', @@ -339,10 +339,12 @@ describe('SSH PTY reattach when the relay requires source restoration', () => { const message = await spawnError(provider) - expect(message).toContain(SSH_PTY_LIVENESS_UNVERIFIABLE_ERROR) + // The attach answered with an incarnation, so the PTY is proven live; only the retry is blocked. + expect(message).toContain(SSH_PTY_RESTORE_REQUIRED_ERROR) + expect(message).not.toContain(SSH_PTY_LIVENESS_UNVERIFIABLE_ERROR) expect(message).not.toContain(SSH_SESSION_EXPIRED_ERROR) - expect(provider.hasPty('ssh:conn-1@@pty-old')).toBe(false) - await expect(provider.probePtyLiveness('ssh:conn-1@@pty-old')).resolves.toBeNull() + expect(provider.hasPty('ssh:conn-1@@pty-old')).toBe(true) + await expect(provider.probePtyLiveness('ssh:conn-1@@pty-old')).resolves.toBe(true) expect(mux.request.mock.calls.filter((call) => call[0] === 'pty.attach')).toHaveLength(1) expect(mux.request.mock.calls.filter((call) => call[0] === 'pty.spawn')).toHaveLength(0) expect(mux.request).toHaveBeenCalledWith('pty.cancelDelivery', { diff --git a/src/main/providers/ssh-pty-session-reattach.ts b/src/main/providers/ssh-pty-session-reattach.ts index 2b2aa8d04c2..5fb8cc2460c 100644 --- a/src/main/providers/ssh-pty-session-reattach.ts +++ b/src/main/providers/ssh-pty-session-reattach.ts @@ -246,8 +246,11 @@ export async function reattachSshPtySessionForSpawn( if (unresumable.sourceActivationLease) { const canceled = await unresumable.sourceActivationLease.rollback() if (!canceled) { + // Why: the attach answered with an incarnation, so the PTY is live; only its delivery + // is stuck, and an unproven cancellation just blocks the second attach. + args.acceptLivePty(unresumable.id) throw new Error( - `${SSH_PTY_LIVENESS_UNVERIFIABLE_ERROR}: ${toRelaySshPtyId(args.connectionId, unresumable.id)}` + `${SSH_PTY_RESTORE_REQUIRED_ERROR}: ${toRelaySshPtyId(args.connectionId, unresumable.id)}` ) } }