fix(runtime): narrow terminal close race settlement

This commit is contained in:
OrcaWin
2026-08-20 12:18:48 -07:00
parent c19d628719
commit 240664ada0
4 changed files with 56 additions and 9 deletions
@@ -59,7 +59,8 @@ jobs:
pnpm run test:e2e
tests/e2e/restart-restore-terminal-input.spec.ts
tests/e2e/terminal-restart-persistence.spec.ts
--grep "clean restart with a live daemon session|cold-restored pane accepts typing|daemon snapshot relaunch preserves"
tests/e2e/windows-headless-terminal-close-lifecycle.spec.ts
--grep "clean restart with a live daemon session|cold-restored pane accepts typing|daemon snapshot relaunch preserves|physical Windows exit wins the headless-tab close race"
--workers=1
- name: Upload Playwright traces
+46 -1
View File
@@ -31441,7 +31441,6 @@ describe('OrcaRuntimeService', () => {
runtime.syncWindowGraph(0, { tabs: [], leaves: [] })
await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)
const handle = runtime.preAllocateHandleForPty(ptyId)
vi.spyOn(runtime, 'closeMobileSessionTab').mockRejectedValue(new Error('tab_not_found'))
await expect(runtime.closeTerminal(handle)).resolves.toEqual({
handle,
@@ -31449,6 +31448,52 @@ describe('OrcaRuntimeService', () => {
ptyKilled: true
})
expect(kill).toHaveBeenCalledWith(ptyId)
expect((await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)).tabs).toEqual([])
})
it('does not mask a missing durable tab while its headless surface remains live', async () => {
const ptyId = 'headless-worker-live-tab'
const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession(
makeWorkspaceSessionWithHeadlessTerminal({
tabsByWorktree: {
[TEST_WORKTREE_ID]: [
{
id: 'host-tab',
ptyId,
worktreeId: TEST_WORKTREE_ID,
title: 'Headless worker',
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 1
}
]
},
terminalLayoutsByTabId: {
'host-tab': makeHeadlessTerminalLayout({ [HEADLESS_LEAF_ID]: ptyId })
}
})
)
const kill = vi.fn(() => false)
const runtime = new OrcaRuntimeService(runtimeStore as never)
runtime.setPtyController({
write: () => true,
kill,
getForegroundProcess: async () => null,
listProcesses: async () => []
})
runtime.registerPty(ptyId, TEST_WORKTREE_ID, null, {
tabId: 'host-tab',
leafId: HEADLESS_LEAF_ID
})
runtime.syncWindowGraph(0, { tabs: [], leaves: [] })
await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)
const handle = runtime.preAllocateHandleForPty(ptyId)
vi.spyOn(runtime, 'closeMobileSessionTab').mockRejectedValue(new Error('tab_not_found'))
await expect(runtime.closeTerminal(handle)).rejects.toThrow('tab_not_found')
expect(kill).toHaveBeenCalledWith(ptyId)
expect((await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)).tabs).not.toEqual([])
})
it('keeps the renderer close transaction for an adopted runtime-owned tab', async () => {
+7 -6
View File
@@ -29820,16 +29820,17 @@ export class OrcaRuntimeService {
try {
await this.closeMobileSessionTab(`id:${pty.pty.worktreeId}`, tabId)
} catch (error) {
// Exit handling or another host-tab transaction may retire this surface first.
if (
!(error instanceof Error) ||
(error.message !== 'workspace_session_unavailable' &&
error.message !== 'tab_not_found')
) {
if (!(error instanceof Error)) {
throw error
}
if (error.message === 'workspace_session_unavailable') {
this.notifier?.closeTerminal(tabId)
} else if (
error.message !== 'tab_not_found' ||
this.findMobileTerminalSurface(pty.pty.worktreeId, tabId)
) {
// Only absence in both persistence and the live publication proves another owner finished teardown.
throw error
}
}
} else {
@@ -118,7 +118,7 @@ test('physical Windows exit wins the headless-tab close race', async ({ testRepo
const processBefore = await queryWindowsAncestry(childPid!)
expect(processBefore).toMatchObject([
{ pid: childPid, name: 'node.exe' },
{ name: 'pwsh.exe' },
{ name: expect.stringMatching(/^(?:pwsh|powershell)\.exe$/i) },
{ name: 'electron.exe' }
])
const ptyShellPid = processBefore[1]!.pid