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)