Skip to content

Commit 0c9fea4

Browse files
Fix agent manager cli spawning on Windows (#4549)
* Add failing windows tests * Make tests fail * Make tests green
1 parent 0f18fe5 commit 0c9fea4

2 files changed

Lines changed: 113 additions & 1 deletion

File tree

src/core/kilocode/agent-manager/CliProcessHandler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,13 @@ export class CliProcessHandler {
159159
const env = this.buildEnvWithApiConfiguration(options?.apiConfiguration)
160160

161161
// Spawn CLI process
162+
// On Windows, .cmd files are batch scripts that require shell execution
163+
const needsShell = process.platform === "win32" && cliPath.toLowerCase().endsWith(".cmd")
162164
const proc = spawn(cliPath, cliArgs, {
163165
cwd: workspace,
164166
stdio: ["pipe", "pipe", "pipe"],
165167
env,
166-
shell: false,
168+
shell: needsShell,
167169
})
168170

169171
if (proc.pid) {

src/core/kilocode/agent-manager/__tests__/CliProcessHandler.spec.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,116 @@ describe("CliProcessHandler", () => {
316316
if (previousProviderType === undefined) delete process.env.KILO_PROVIDER_TYPE
317317
else process.env.KILO_PROVIDER_TYPE = previousProviderType
318318
})
319+
320+
describe("Windows .cmd file handling", () => {
321+
it("uses shell: true for .cmd files on Windows", () => {
322+
const originalPlatform = process.platform
323+
Object.defineProperty(process, "platform", { value: "win32", configurable: true })
324+
325+
try {
326+
const onCliEvent = vi.fn()
327+
handler.spawnProcess(
328+
"C:\\Users\\test\\.kilocode\\cli\\pkg\\node_modules\\.bin\\kilocode.cmd",
329+
"/workspace",
330+
"test prompt",
331+
undefined,
332+
onCliEvent,
333+
)
334+
335+
expect(spawnMock).toHaveBeenCalledWith(
336+
"C:\\Users\\test\\.kilocode\\cli\\pkg\\node_modules\\.bin\\kilocode.cmd",
337+
expect.any(Array),
338+
expect.objectContaining({ shell: true }),
339+
)
340+
} finally {
341+
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true })
342+
}
343+
})
344+
345+
it("uses shell: true for .CMD files (case insensitive) on Windows", () => {
346+
const originalPlatform = process.platform
347+
Object.defineProperty(process, "platform", { value: "win32", configurable: true })
348+
349+
try {
350+
const onCliEvent = vi.fn()
351+
handler.spawnProcess(
352+
"C:\\Users\\test\\kilocode.CMD",
353+
"/workspace",
354+
"test prompt",
355+
undefined,
356+
onCliEvent,
357+
)
358+
359+
expect(spawnMock).toHaveBeenCalledWith(
360+
"C:\\Users\\test\\kilocode.CMD",
361+
expect.any(Array),
362+
expect.objectContaining({ shell: true }),
363+
)
364+
} finally {
365+
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true })
366+
}
367+
})
368+
369+
it("uses shell: false for non-.cmd executables on Windows", () => {
370+
const originalPlatform = process.platform
371+
Object.defineProperty(process, "platform", { value: "win32", configurable: true })
372+
373+
try {
374+
const onCliEvent = vi.fn()
375+
handler.spawnProcess(
376+
"C:\\Users\\test\\kilocode.exe",
377+
"/workspace",
378+
"test prompt",
379+
undefined,
380+
onCliEvent,
381+
)
382+
383+
expect(spawnMock).toHaveBeenCalledWith(
384+
"C:\\Users\\test\\kilocode.exe",
385+
expect.any(Array),
386+
expect.objectContaining({ shell: false }),
387+
)
388+
} finally {
389+
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true })
390+
}
391+
})
392+
393+
it("uses shell: false on macOS regardless of extension", () => {
394+
const originalPlatform = process.platform
395+
Object.defineProperty(process, "platform", { value: "darwin", configurable: true })
396+
397+
try {
398+
const onCliEvent = vi.fn()
399+
handler.spawnProcess("/usr/local/bin/kilocode", "/workspace", "test prompt", undefined, onCliEvent)
400+
401+
expect(spawnMock).toHaveBeenCalledWith(
402+
"/usr/local/bin/kilocode",
403+
expect.any(Array),
404+
expect.objectContaining({ shell: false }),
405+
)
406+
} finally {
407+
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true })
408+
}
409+
})
410+
411+
it("uses shell: false on Linux regardless of extension", () => {
412+
const originalPlatform = process.platform
413+
Object.defineProperty(process, "platform", { value: "linux", configurable: true })
414+
415+
try {
416+
const onCliEvent = vi.fn()
417+
handler.spawnProcess("/usr/bin/kilocode", "/workspace", "test prompt", undefined, onCliEvent)
418+
419+
expect(spawnMock).toHaveBeenCalledWith(
420+
"/usr/bin/kilocode",
421+
expect.any(Array),
422+
expect.objectContaining({ shell: false }),
423+
)
424+
} finally {
425+
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true })
426+
}
427+
})
428+
})
319429
})
320430

321431
describe("session_created event handling", () => {

0 commit comments

Comments
 (0)