Skip to content

Commit 9c8306a

Browse files
committed
fix(desktop): run Windows terminals on the bundled ConPTY, which keeps output in order
The ConPTY built into Windows passes OSC sequences through ahead of text it has not rendered yet, so the finish marker of a quick command often arrived before its output and the agent got an empty result (a quarter to two fifths of runs on Windows Server 2025). node-pty's bundled conpty.dll and OpenConsole keep the stream in order. The Windows suite now runs a dozen commands per shell so a lost output shows.
1 parent ee2aca9 commit 9c8306a

2 files changed

Lines changed: 20 additions & 9 deletions

File tree

‎apps/desktop/src/main/terminal/session.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,9 @@ export class TerminalSession {
422422
rows: options.rows,
423423
cwd: options.cwd,
424424
env: shellEnv,
425+
// The ConPTY built into Windows can deliver an OSC marker ahead of the text written before
426+
// it, so a command's output lands after its finish marker; the bundled build keeps order.
427+
...(platform === 'win32' ? { useConptyDll: true } : {}),
425428
})
426429

427430
logger.info('Started terminal session', { shell: shellPath, instrumented: shell !== null })

‎apps/desktop/src/main/terminal/windows-shells.test.ts‎

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ const WINDOWS_POWERSHELL = join(
2929
interface ShellUnderTest {
3030
name: string
3131
path: string
32-
succeed: string
32+
/** Prints `sim-ok-<n>`. */
33+
succeed(n: number): string
3334
fail: string
3435
/** A cmdlet that fails, which sets no exit code of its own. */
3536
failingCmdlet?: string
@@ -41,7 +42,7 @@ interface ShellUnderTest {
4142
}
4243

4344
const POWERSHELL: Omit<ShellUnderTest, 'name' | 'path'> = {
44-
succeed: 'Write-Output sim-ok',
45+
succeed: (n) => `Write-Output sim-ok-${n}`,
4546
fail: 'cmd /c exit 3',
4647
failingCmdlet: 'Get-Item -LiteralPath C:\\sim-does-not-exist',
4748
quote: (path) => `'${path}'`,
@@ -55,7 +56,7 @@ const SHELLS: ShellUnderTest[] = [
5556
{
5657
name: 'Git Bash',
5758
path: 'C:\\Program Files\\Git\\bin\\bash.exe',
58-
succeed: 'echo sim-ok',
59+
succeed: (n) => `echo sim-ok-${n}`,
5960
fail: "sh -c 'exit 3'",
6061
quote: (path) => `'${path.replaceAll('\\', '/')}'`,
6162
cd: 'cd',
@@ -70,6 +71,9 @@ Set-Content -LiteralPath $PidFile -Value $PID
7071
Start-Sleep -Seconds 120
7172
`
7273

74+
/** Enough runs that output lost to a reordering ConPTY (a quarter of runs or more) shows. */
75+
const OUTPUT_RUNS = 12
76+
7377
/** A shell's first start on a cold CI runner, then the Stop escalation, take a while. */
7478
const SHELL_TEST_TIMEOUT_MS = 90_000
7579

@@ -115,12 +119,16 @@ describe.runIf(process.platform === 'win32')('the agent terminal on Windows', ()
115119
const installed = existsSync(shell.path)
116120

117121
it.runIf(installed)(
118-
"reports a successful command's output and exit code",
122+
"reports each successful command's output and exit code",
119123
async () => {
120-
const result = await run(open(shell), 'call-ok', shell.succeed)
121-
122-
expect(result).toMatchObject({ status: 'completed', exitCode: 0 })
123-
expect(result.output).toContain('sim-ok')
124+
const terminal = open(shell)
125+
// Repeated: a ConPTY that reorders output past the finish marker loses it only some runs.
126+
for (let n = 0; n < OUTPUT_RUNS; n++) {
127+
const result = await run(terminal, `call-ok-${n}`, shell.succeed(n))
128+
129+
expect(result).toMatchObject({ status: 'completed', exitCode: 0 })
130+
expect(result.output).toContain(`sim-ok-${n}`)
131+
}
124132
},
125133
SHELL_TEST_TIMEOUT_MS
126134
)
@@ -190,7 +198,7 @@ describe.runIf(process.platform === 'win32')('the agent terminal on Windows', ()
190198
await expect(terminal.cancelTool('call-stubborn')).resolves.toBe(true)
191199
expect(await running).toMatchObject({ status: 'completed' })
192200
expect(isAlive(pid)).toBe(false)
193-
expect(await run(terminal, 'call-after', shell.succeed)).toMatchObject({ exitCode: 0 })
201+
expect(await run(terminal, 'call-after', shell.succeed(0))).toMatchObject({ exitCode: 0 })
194202
},
195203
SHELL_TEST_TIMEOUT_MS
196204
)

0 commit comments

Comments
 (0)