Repository navigation
Description
squad loop preflight fails on Windows — execFile('copilot', ['--version']) can't resolve copilot.ps1/.cmd (missing shell flag)
Summary
On Windows, squad loop aborts with "Copilot CLI required" even when the Copilot CLI is installed and working. The preflight check uses execFile('copilot', ['--version']) without shell: true, so it can't resolve the copilot.ps1 / copilot.cmd shim that the CLI ships as on Windows, and false-negatives.
This is an inconsistency regression: the same check was fixed in sibling call sites (doctor, monitor-email, monitor-teams, agent-spawn) but loop was missed.
Where
packages/squad-cli/src/cli/commands/loop.ts:
// ~line 250
async function checkCopilotCli(): Promise<void> {
return new Promise<void>((resolve, reject) => {
execFile('copilot', ['--version'], (err) => { // ← no `shell` option
if (err) reject(err);
else resolve();
});
});
}On rejection the run aborts (~line 322):
fatal('Copilot CLI required. Install from https://cli.github.com/ and run `gh extension install github/gh-copilot`');Root cause
On Windows the Copilot CLI is not a copilot.exe — it's installed as a copilot.ps1 / copilot.cmd shim. Node's child_process.execFile/spawn does not consult PATHEXT unless shell: true, so a bare execFile('copilot', ...) looks only for an exact-named executable, fails with ENOENT, and the preflight concludes the CLI is missing. The loop then exits in ~0.5s having done no work.
This is already fixed elsewhere — loop was just missed
| Call site | Windows-safe? |
|---|---|
commands/loop.ts checkCopilotCli |
❌ execFile('copilot', ['--version'], cb) — no shell |
commands/doctor.ts |
✅ { shell: true, timeout: 5000 } |
commands/watch/capabilities/monitor-email.ts |
✅ { shell: true, timeout: 5000 } |
commands/watch/capabilities/monitor-teams.ts |
✅ { shell: true, timeout: 5000 } |
commands/watch/agent-spawn.ts resolveCopilotCmd |
✅ { shell: IS_WINDOWS } + falls back to gh copilot |
The CHANGELOG even records the fix wave: "Fix Windows spawn EINVAL in watch capabilities by adding shell: true to execFile() calls in monitor-teams, monitor-email, retro, and decision-hygiene… (closes #920, #880, #1107)" — but loop (and anything routing through its preflight) wasn't included.
Repro
On Windows, with the standalone copilot CLI installed as copilot.ps1 on PATH (verify: Get-Command copilot shows an ExternalScript/Application .ps1/.cmd, not a .exe):
cd <a squad-initialized repo>
squad loop --file loop.md
Expected: the loop runs the configured agent.
Actual: Copilot CLI required. Install from https://cli.github.com/ … and immediate exit — even though copilot --version works in the same shell.
Suggested fix
Mirror the sibling sites — add the shell flag (platform-scoped) to checkCopilotCli:
execFile('copilot', ['--version'], { shell: process.platform === 'win32', timeout: 5000 }, (err) => {
if (err) reject(err);
else resolve();
});Even better: reuse resolveCopilotCmd() from watch/agent-spawn.ts as the single source of truth — it already handles shell: IS_WINDOWS and falls back to gh copilot, so loop would also work for users who only have the gh extension rather than the standalone CLI. Worth auditing triage / any other command that preflights copilot the same way.
Environment
- OS: Windows
- Copilot CLI: standalone, installed as
copilot.ps1on PATH - Symptom reproduced and root-caused while building a Windows integration on top of
squad loop; worked around downstream by spawningcopilotdirectly viapwsh -NoProfile -File copilot.ps1.
Activity
test(qa): regression coverage for bradygaster/squad#1372 — Windows .c…
test(regression): spawn-side shell:true injection guard (security fol…
chore(release): consolidated transplant bundle for bradygaster/squad#…
duongau commentedon Jun 23, 2026
Independent corroboration from
microsoft/conductorFlagging that Microsoft's Conductor project hit this exact Windows launcher bug. Their README's Windows/Development section documents it verbatim:
Same root cause as the
squad looppreflight failure here: a no-shellexecFile/subprocesscall to barecopilotcan't resolve the Windows.ps1/.cmdshim via PATHEXT. Conductor's mitigation is aCOPILOT_CLI_PATHenv-var override honored by the Copilot SDK.Two suggestions for a fix:
copilot.ps1->pwsh -NoProfile -File,copilot.cmd/.bat->cmd /c) and invoke it directly withshell: false, rather than relying on PATHEXT. Note: keepshell: falseso a large multi-line-pprompt argument isn't mangled by the shell.COPILOT_CLI_PATHoverride as an escape hatch for nonstandard installs (matching the Copilot SDK / Conductor convention).Given two separate Microsoft projects independently hitting this on Windows, it'd be great to prioritize. Happy to send a PR if useful.
fix: respect SQUAD_HOME env var, implement SQUAD_PERSONAL_DIR, fix Wi…