diff --git a/src/main/ipc/pty.ts b/src/main/ipc/pty.ts index ffd4144366b..2962c39cef0 100644 --- a/src/main/ipc/pty.ts +++ b/src/main/ipc/pty.ts @@ -3910,7 +3910,9 @@ export function registerPtyHandlers( return release }, finalizeExit: (event) => { - runtime?.onPtyExit(event.id, event.code, event.ptyIncarnation) + runtime?.onPtyExit(event.id, event.code, event.ptyIncarnation, { + hostExitConfirmed: true + }) finalizePtyExitForRenderer(event) }, pauseProvider: (generation, id) => { diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index ca45b26d284..8dfb39ca0e3 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -14712,13 +14712,21 @@ export class OrcaRuntimeService { } } - onPtyExit(ptyId: string, exitCode: number, exitIncarnationId?: PtyIncarnationId): void { + onPtyExit( + ptyId: string, + exitCode: number, + exitIncarnationId?: PtyIncarnationId, + options?: { hostExitConfirmed?: boolean } + ): void { const pty = this.ptysById.get(ptyId) if (exitIncarnationId && pty?.incarnationId && exitIncarnationId !== pty.incarnationId) { return } const preservesAbnormalSshSurface = - this.isSshOwnedPtyId(ptyId) && pty?.connectionId != null && exitCode < 0 + this.isSshOwnedPtyId(ptyId) && + pty?.connectionId != null && + exitCode < 0 && + options?.hostExitConfirmed !== true if (preservesAbnormalSshSurface) { if (this.getPtyLivenessVerdict(ptyId)?.status !== 'unverifiable') { this.markPtyLivenessUnverifiable(ptyId, SSH_EXIT_UNCONFIRMED_REASON) @@ -14855,7 +14863,7 @@ export class OrcaRuntimeService { this.setPairedRendererSessionOwnership(pty.ptyId, false) pty.disconnectedAt = Date.now() pty.lastExitCode = exitCode - if (exitCode >= 0) { + if (exitCode >= 0 || options?.hostExitConfirmed === true) { // A real wait status from the owning host is the death certificate; the // synthetic -1 we emit on a failed/unroutable stop is not. this.forgetPtyLivenessVerdict(ptyId) diff --git a/src/main/runtime/pty-inventory-liveness-verdict.test.ts b/src/main/runtime/pty-inventory-liveness-verdict.test.ts index 394d7b7ed8f..38e3dda20eb 100644 --- a/src/main/runtime/pty-inventory-liveness-verdict.test.ts +++ b/src/main/runtime/pty-inventory-liveness-verdict.test.ts @@ -83,6 +83,15 @@ describe('inventory sweep liveness verdicts', () => { }) }) + it('accepts a current owning-host exit even when its numeric code is negative', () => { + const runtime = makeRuntimeMissingFromInventory(() => null) + runtime.markPtyLivenessUnverifiable(REMOTE_PTY_ID, 'inventory transport failed') + + runtime.onPtyExit(REMOTE_PTY_ID, -1, undefined, { hostExitConfirmed: true }) + + expect(runtime.getPtyLivenessVerdict(REMOTE_PTY_ID)).toBeNull() + }) + it('records lost contact when no provider can answer for the PTY', async () => { const runtime = makeRuntimeMissingFromInventory(() => null)