fix(terminal): quit no longer warns about terminals a paired Orca server keeps running (#30006)

A paired runtime (orca serve, managed SSH/WSL orcad) owns its terminals and keeps
them after this client quits, so only SSH relay terminals, which a relay kills
after its grace period, still raise the quit warning. Window close is unchanged.

Fixes #29916
This commit is contained in:
Neil
2026-10-17 00:16:24 -07:00
committed by GitHub
parent 035588e914
commit 29fa3cf9fb
2 changed files with 42 additions and 10 deletions
@@ -81,22 +81,52 @@ describe('assessWindowCloseRunningWork', () => {
})
})
it('warns on quit about a live process on a paired runtime host', async () => {
// #29916: a paired runtime owns its terminals and keeps them after this client quits.
it('does not warn on quit, or probe, for a live process on a paired runtime host', async () => {
setState([RUNTIME_PTY, OWNERLESS_RUNTIME_PTY])
answerWith({ [RUNTIME_PTY]: BUSY, [OWNERLESS_RUNTIME_PTY]: BUSY })
await expect(assessWindowCloseRunningWork({ isQuitting: true })).resolves.toEqual({
kind: 'none'
})
expect(inspectRuntimeTerminalProcessMock).not.toHaveBeenCalled()
})
it('does not warn on quit when a paired runtime host is unreachable', async () => {
setState([RUNTIME_PTY])
answerWith({ [RUNTIME_PTY]: UNVERIFIABLE })
await expect(assessWindowCloseRunningWork({ isQuitting: true })).resolves.toEqual({
kind: 'none'
})
})
it('still warns when closing the window over a live process on a paired runtime host', async () => {
setState([RUNTIME_PTY])
answerWith({ [RUNTIME_PTY]: BUSY })
await expect(assessWindowCloseRunningWork({ isQuitting: true })).resolves.toEqual({
await expect(assessWindowCloseRunningWork({ isQuitting: false })).resolves.toEqual({
kind: 'running'
})
})
it('counts an owner-less remote pty as remote work', async () => {
it('still warns when closing the window over an owner-less runtime pty', async () => {
setState([OWNERLESS_RUNTIME_PTY])
answerWith({ [OWNERLESS_RUNTIME_PTY]: BUSY })
answerWith({ [OWNERLESS_RUNTIME_PTY]: UNVERIFIABLE })
await expect(assessWindowCloseRunningWork({ isQuitting: false })).resolves.toEqual({
kind: 'unverifiable'
})
})
it('warns on quit about an SSH relay pty alongside a busy paired runtime pty', async () => {
setState([SSH_PTY, RUNTIME_PTY])
answerWith({ [SSH_PTY]: BUSY, [RUNTIME_PTY]: BUSY })
await expect(assessWindowCloseRunningWork({ isQuitting: true })).resolves.toEqual({
kind: 'running'
})
expect(inspectRuntimeTerminalProcessMock).toHaveBeenCalledTimes(1)
})
// The crux of docs/reference/ssh-execution-boundary.md: an unreachable host is `unverifiable`,
@@ -155,7 +185,7 @@ describe('assessWindowCloseRunningWork', () => {
setState([SSH_PTY, RUNTIME_PTY])
answerWith({ [SSH_PTY]: UNVERIFIABLE, [RUNTIME_PTY]: BUSY })
await expect(assessWindowCloseRunningWork({ isQuitting: true })).resolves.toEqual({
await expect(assessWindowCloseRunningWork({ isQuitting: false })).resolves.toEqual({
kind: 'running'
})
})
@@ -1,5 +1,5 @@
import { useAppStore } from '@/store'
import { isRemoteExecutionHostPtyId } from '../../../../shared/remote-execution-host-pty-id'
import { parseAppSshPtyId } from '../../../../shared/ssh-pty-id'
import { collectTabPtyIds } from './running-terminal-close-guard'
import { probePtyRunningWork } from './pty-running-work-probe'
@@ -32,10 +32,12 @@ export type WindowCloseRunningWork =
*
* Two deliberate asymmetries:
*
* - **Quit only considers remote ptys.** Quitting is an unambiguous instruction to end this
* - **Quit only considers SSH relay ptys.** Quitting is an unambiguous instruction to end this
* machine's processes (#524), but it is not an instruction to end execution on someone else's:
* the client detaches while the relay keeps running, and a target with a bounded grace period
* then SIGKILLs that work once the countdown expires.
* an SSH relay (`ssh:` ids) keeps its terminals only for its grace period after the client
* detaches, then kills them. A paired runtime (`remote:` ids: `orca serve`, managed SSH/WSL
* orcad) owns its terminals outright and keeps them after the client quits, so quitting
* endangers nothing there, reachable or not (#29916).
* - **Only a remote `unverifiable` warns.** A local probe has no transport to lose, so its failure
* means the pty is gone. A remote one that cannot be reached is the case
* `docs/reference/ssh-execution-boundary.md` exists to protect: loss of contact is not evidence
@@ -51,7 +53,7 @@ export async function assessWindowCloseRunningWork(params: {
.flatMap((tab) => collectTabPtyIds(state, tab.id))
)
const candidatePtyIds = params.isQuitting
? [...ptyIds].filter(isRemoteExecutionHostPtyId)
? [...ptyIds].filter((ptyId) => parseAppSshPtyId(ptyId) !== null)
: [...ptyIds]
if (candidatePtyIds.length === 0) {
return { kind: 'none' }