From 6cc1c137ec76c6343f4709ec6755f67fa8bfcd9f Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Tue, 25 Aug 2026 05:28:18 -0400 Subject: [PATCH] fix: settle unavailable PTY process inspection --- ...pty-session-liveness-and-ownership.test.ts | 28 +++++++++++++++++++ src/main/ipc/pty/ipc/inspect.ts | 17 +++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/main/ipc/pty-session-liveness-and-ownership.test.ts b/src/main/ipc/pty-session-liveness-and-ownership.test.ts index 97c0e614933..42a6582bd8c 100644 --- a/src/main/ipc/pty-session-liveness-and-ownership.test.ts +++ b/src/main/ipc/pty-session-liveness-and-ownership.test.ts @@ -9,6 +9,7 @@ import { setLocalPtyProvider, unregisterSshPtyProvider } from './pty' +import { TerminalSessionOwnerUnverifiedError } from '../daemon/daemon-errors' vi.mock('electron', () => import('./pty-ipc-mock-registry').then((m) => m.electronModuleMock())) vi.mock('fs', () => import('./pty-ipc-mock-registry').then((m) => m.fsModuleMock())) @@ -516,4 +517,31 @@ describe('registerPtyHandlers', () => { unavailable: true }) }) + it('preserves unknown liveness while settling structured process inspection', async () => { + const unavailable = vi.fn(() => { + throw new TerminalSessionOwnerUnverifiedError('saved-pty') + }) + registerPtyHandlers(mainWindow as never) + setLocalPtyProvider({ + hasChildProcesses: unavailable, + getForegroundProcess: unavailable, + confirmForegroundProcess: unavailable, + inspectProcess: unavailable + } as never) + + await expect(handlers.get('pty:hasChildProcesses')!(null, { id: 'saved-pty' })).rejects.toThrow( + TerminalSessionOwnerUnverifiedError + ) + await expect( + handlers.get('pty:getForegroundProcess')!(null, { id: 'saved-pty' }) + ).rejects.toThrow(TerminalSessionOwnerUnverifiedError) + await expect( + handlers.get('pty:confirmForegroundProcess')!(null, { id: 'saved-pty' }) + ).rejects.toThrow(TerminalSessionOwnerUnverifiedError) + await expect(handlers.get('pty:inspectProcess')!(null, { id: 'saved-pty' })).resolves.toEqual({ + foregroundProcess: null, + hasChildProcesses: false, + unavailable: true + }) + }) }) diff --git a/src/main/ipc/pty/ipc/inspect.ts b/src/main/ipc/pty/ipc/inspect.ts index f4fbe60d7ec..e587b250b9c 100644 --- a/src/main/ipc/pty/ipc/inspect.ts +++ b/src/main/ipc/pty/ipc/inspect.ts @@ -28,6 +28,7 @@ import { settlePendingPaneSerializer } from '../pane/serializer-state' import { installPtyHasPtyIpc } from './has-pty' +import { TerminalSessionOwnerUnverifiedError } from '../../../daemon/daemon-errors' export function installPtyInspectIpcHandlers(deps: { store?: Store @@ -212,15 +213,27 @@ export function installPtyInspectIpcHandlers(deps: { ipcMain.handle('pty:inspectProcess', async (_event, args: { id: string }) => { // Why: same routing hazard as pty:hasPty — an unroutable id must read as unavailable, not as a local-provider answer or a raised IPC error. + const unavailable = { + foregroundProcess: null, + hasChildProcesses: false, + unavailable: true as const + } if ( typeof args?.id !== 'string' || !args.id || args.id.startsWith('remote:') || !hasPtyProviderForInspection(args.id) ) { - return { foregroundProcess: null, hasChildProcesses: false, unavailable: true as const } + return unavailable + } + try { + return await inspectPtyProviderProcessForRenderer(getProviderForPty(args.id), args.id) + } catch (error) { + if (error instanceof TerminalSessionOwnerUnverifiedError) { + return unavailable + } + throw error } - return inspectPtyProviderProcessForRenderer(getProviderForPty(args.id), args.id) }) ipcMain.handle(