diff --git a/src/main/providers/local-pty-provider-shutdown.test.ts b/src/main/providers/local-pty-provider-shutdown.test.ts index cccede7186b..950a7e9cbae 100644 --- a/src/main/providers/local-pty-provider-shutdown.test.ts +++ b/src/main/providers/local-pty-provider-shutdown.test.ts @@ -566,6 +566,20 @@ describe('LocalPtyProvider', () => { expect(list).toHaveLength(0) }) + it('force-kills an OMP PTY root during app quit', async () => { + const killSpy = vi.fn() + spawnMock.mockReturnValue({ + ...mockProc, + kill: killSpy + }) + + await provider.spawn({ cols: 80, rows: 24, launchAgent: 'omp' }) + + provider.killAll() + + expect(killSpy).toHaveBeenCalledWith('SIGKILL') + }) + it('does not destroy after intentional Windows orphan kills', async () => { Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) const destroySpy = vi.fn() diff --git a/src/main/providers/local-pty-termination.ts b/src/main/providers/local-pty-termination.ts index 7a19e9d7326..426a7aad346 100644 --- a/src/main/providers/local-pty-termination.ts +++ b/src/main/providers/local-pty-termination.ts @@ -243,7 +243,13 @@ export function killAllLocalPtys(): void { disposePtyExitListener(id) if (!(process.platform === 'win32' && ptyTerminationMode.has(id))) { try { - proc.kill() + if (ptyAgentSessionIds.has(id) && process.platform !== 'win32') { + // App quit is synchronous, so sweep attached agent process groups before + // releasing node-pty; otherwise OMP workers can outlive the foreground PTY. + forceKillPosixPtyProcessGroups(proc.pid, () => proc.kill('SIGKILL')) + } else { + proc.kill() + } } catch { /* Process may already be dead. */ }