fix(runtime): preserve headless close durability on notifier failure

This commit is contained in:
Brennan Benson
2026-07-16 18:26:08 -07:00
parent 48d9b4bf5f
commit 9dfdf3f643
2 changed files with 30 additions and 9 deletions
+15 -7
View File
@@ -19722,11 +19722,9 @@ describe('OrcaRuntimeService', () => {
expect(getSession().terminalLayoutsByTabId['host-tab']).toBeUndefined()
})
it('tears down a runtime-owned SSH headless tab authoritatively even when the renderer relay is wired', async () => {
// #8958 regression: with a window attached, both closeTerminal and
// closeTerminalTab are wired. A headless SSH tab is absent from the window
// graph, so the closeTerminalTab relay would resolve nothing and ack success
// while the PTY kept running. The authoritative teardown must win.
it('durably tears down a runtime-owned SSH headless tab when renderer cleanup fails', async () => {
// #8958 regression: the acknowledged renderer relay cannot see headless tabs;
// its advisory fallback must not prevent authoritative teardown or flushing.
const persistedPtyId = 'ssh:ssh-1@@relay-pty'
const { runtimeStore, getSession } = makeRuntimeStoreWithWorkspaceSession(
makeWorkspaceSessionWithHeadlessTerminal({
@@ -19750,9 +19748,14 @@ describe('OrcaRuntimeService', () => {
})
)
const kill = vi.fn(() => true)
const closeTerminal = vi.fn()
const flushOrThrow = vi.fn()
const rendererError = new Error('renderer unavailable')
const closeTerminal = vi.fn(() => {
throw rendererError
})
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
const closeTerminalTab = vi.fn(async () => {})
const runtime = new OrcaRuntimeService(runtimeStore as never)
const runtime = new OrcaRuntimeService({ ...runtimeStore, flushOrThrow } as never)
runtime.setPtyController({
write: () => true,
kill,
@@ -19767,6 +19770,11 @@ describe('OrcaRuntimeService', () => {
expect(closeTerminalTab).not.toHaveBeenCalled()
expect(kill).toHaveBeenCalledWith(persistedPtyId)
expect(closeTerminal).toHaveBeenCalledWith('host-tab')
expect(flushOrThrow).toHaveBeenCalledTimes(1)
expect(warn).toHaveBeenCalledWith(
'[runtime] failed to notify renderer after headless terminal close',
{ parentTabId: 'host-tab', error: rendererError }
)
expect(getSession().tabsByWorktree[TEST_WORKTREE_ID]).toEqual([])
expect(getSession().terminalLayoutsByTabId['host-tab']).toBeUndefined()
})
+15 -2
View File
@@ -4692,7 +4692,7 @@ export class OrcaRuntimeService {
// renderer's live pin guard and durable close transaction.
if (closingWholeParent && !this.tabs.has(tab.parentTabId)) {
this.closeHeadlessMobileTerminalTab(worktreeId, snapshot!, tab)
this.notifier?.closeTerminal(tab.parentTabId)
this.notifyRendererOfHeadlessTerminalClose(tab.parentTabId)
this.store?.flushOrThrow?.()
return { closed: true }
}
@@ -4706,7 +4706,7 @@ export class OrcaRuntimeService {
// only raw pane close. Runtime-owned parents still need de-persist + kill.
if (closingWholeParent && this.isRuntimeOwnedHeadlessMobileTab(worktreeId, tab)) {
this.closeHeadlessMobileTerminalTab(worktreeId, snapshot!, tab)
this.notifier?.closeTerminal(tab.parentTabId)
this.notifyRendererOfHeadlessTerminalClose(tab.parentTabId)
this.store?.flushOrThrow?.()
return { closed: true }
}
@@ -4739,6 +4739,19 @@ export class OrcaRuntimeService {
return { closed: true }
}
private notifyRendererOfHeadlessTerminalClose(parentTabId: string): void {
// Why: this relay is advisory after main owns teardown; renderer failure must
// not prevent the authoritative session flush or turn the close into failure.
try {
this.notifier?.closeTerminal(parentTabId)
} catch (error) {
console.warn('[runtime] failed to notify renderer after headless terminal close', {
parentTabId,
error
})
}
}
private async closeHeadlessMobileBrowserTab(
worktreeId: string,
snapshot: RuntimeMobileSessionTabsSnapshot,