Skip to content

Commit f08e8dd

Browse files
authored
Merge branch 'main' into fix/ssrf-approval-callback-435
2 parents 2efee0c + 7784f32 commit f08e8dd

2 files changed

Lines changed: 26 additions & 2 deletions

File tree

desktop/src/main/cli.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
compareVersions,
66
effectiveMinVersion,
77
parseAfVersion,
8+
probeCli,
89
selectCli
910
} from './cli'
1011

@@ -157,3 +158,16 @@ describe('cliCandidates', () => {
157158
expect(sources).toEqual(['managed', 'managed', 'path'])
158159
})
159160
})
161+
162+
describe('probeCli', () => {
163+
it('resolves responds:false when spawn throws synchronously', async () => {
164+
// An empty command makes spawn() throw before any listeners attach —
165+
// the same shape as Windows throwing UNKNOWN for a non-PE binary on PATH.
166+
// probeCli must swallow it: a rejection here fails the whole probeAll and
167+
// the app never creates its tray or window.
168+
await expect(probeCli({ command: '', source: 'path' })).resolves.toMatchObject({
169+
responds: false,
170+
version: null
171+
})
172+
})
173+
})

desktop/src/main/cli.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
// No electron imports: the bundled path is injected by main, so probing,
2626
// selection, and provisioning stay unit-testable.
2727

28-
import { spawn } from 'node:child_process'
28+
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'
2929
import { promises as fs } from 'node:fs'
3030
import { join } from 'node:path'
3131
import type { AgentActionResult, CliStatus } from '../shared/types'
@@ -139,7 +139,17 @@ export function probeCli(candidate: CliCandidate): Promise<ProbedCandidate> {
139139
resolve({ ...candidate, responds, version: responds ? parseAfVersion(output) : null })
140140
}
141141

142-
const child = spawn(candidate.command, ['version'], { windowsHide: true, env: childEnv() })
142+
// spawn() can throw synchronously (e.g. Windows UNKNOWN when the PATH
143+
// resolves `af` to a non-PE file such as a WSL Linux binary). Without the
144+
// try/catch that rejects this promise, and one bad candidate then fails
145+
// the whole probeAll — the app hangs before creating its tray or window.
146+
let child: ChildProcessWithoutNullStreams
147+
try {
148+
child = spawn(candidate.command, ['version'], { windowsHide: true, env: childEnv() })
149+
} catch {
150+
done(false)
151+
return
152+
}
143153
const timer = setTimeout(() => {
144154
child.kill()
145155
done(false)

0 commit comments

Comments
 (0)