From 4b3ccc41584c9630cdafd2d3e709c8ab774e24a7 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:32:29 -0700 Subject: [PATCH] fix(windows): skip the ConPTY input-reader check on WinPTY panes (#36490) node-pty falls back to WinPTY below Windows build 18309. WinPTY never sends ConPTY's DA1 announcement, so every plain terminal was classified as having no input reader and the healthy shell was killed and respawned. On v1.4.244 that discard killed the WinPTY agent twice, and winpty's native kill throws 'Pty seems to have been killed already', failing every plain terminal spawn. Fixes #36317 --- .../pty-subprocess/conpty-startup-input-reader.ts | 10 +++++++++- .../pty-subprocess/native-pty-spawn-windows.test.ts | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/daemon/pty-subprocess/conpty-startup-input-reader.ts b/src/main/daemon/pty-subprocess/conpty-startup-input-reader.ts index a9b4092cc1c..941f6d759b0 100644 --- a/src/main/daemon/pty-subprocess/conpty-startup-input-reader.ts +++ b/src/main/daemon/pty-subprocess/conpty-startup-input-reader.ts @@ -163,6 +163,14 @@ export function _resetConptyStartupTrustForTest(): void { respawnCount = 0 } +type NodePtyWindowsPane = pty.IPty & { _agent?: { _useConpty?: boolean } } + +// node-pty falls back to WinPTY below Windows build 18309, which never announces an input reader. +function runsOnWinpty(proc: pty.IPty): boolean { + const pane: NodePtyWindowsPane = proc + return pane._agent?._useConpty === false +} + function withNotice(claim: () => ConptyStartupEvents): () => ConptyStartupEvents { return () => { const events = claim() @@ -186,7 +194,7 @@ export async function spawnConptyWithInputReader( }> { const firstSpawn = await spawn() const { proc: first, physicalOwner: firstOwner } = firstSpawn - if (announcementUntrusted) { + if (announcementUntrusted || runsOnWinpty(first)) { return { ...firstSpawn, claimStartupEvents: () => ({ data: [], exit: null }) } } const firstStartup = await decideConptyStartup(first, opts.signal, firstOwner) diff --git a/src/main/daemon/pty-subprocess/native-pty-spawn-windows.test.ts b/src/main/daemon/pty-subprocess/native-pty-spawn-windows.test.ts index b5574e20dd3..92c7f18b133 100644 --- a/src/main/daemon/pty-subprocess/native-pty-spawn-windows.test.ts +++ b/src/main/daemon/pty-subprocess/native-pty-spawn-windows.test.ts @@ -367,6 +367,19 @@ describe('Windows shell fallback over node-pty ConPTY', () => { expect(nodePty.spawn).toHaveBeenCalledTimes(3) }) + it('hands out a WinPTY pane unchecked, since only ConPTY announces an input reader', async () => { + // Windows builds below 18309 get WinPTY, whose native kill throws when repeated (#36317). + const winpty = Object.assign(deadPane(990001), { _agent: { _useConpty: false } }) + nodePty.spawn.mockReturnValueOnce(winpty) + + const result = await spawnNativeDaemonPty(args) + + expect(nodePty.spawn).toHaveBeenCalledOnce() + expect(winpty.kill).not.toHaveBeenCalled() + expect(ptyJob.killConptyNow).not.toHaveBeenCalled() + expect(result.process).toBe(winpty) + }) + it('does not respawn a shell that exited before ConPTY announced anything', async () => { const exited = fakePane(990001, [], 3) nodePty.spawn.mockReturnValueOnce(exited)