Skip to content

fix(copilot): detect copilot.exe on Windows instead of assuming copilot.cmd - #4758

Merged
mnriem merged 3 commits into
github:mainfrom
chelsealong:fix/4755-copilot-windows-executable-detection
Sep 28, 2026
Merged

mnriem merged 3 commits into
github:mainfrom
chelsealong:fix/4755-copilot-windows-executable-detection

Conversation

@chelsealong

Copy link
Copy Markdown
Contributor

Fixes #4755

Problem

_copilot_executable() in src/specify_cli/integrations/copilot/__init__.py
hardcoded copilot.cmd as the Windows executable name, assuming the GitHub
Copilot CLI was always installed via an npm global install (which wraps the
binary in a .cmd shim on Windows). Any other install channel — a standalone
installer, winget, scoop, or a signed native binary — puts copilot.exe
on PATH instead. Because Spec Kit never probed PATH, workflow/command-step
dispatch to the copilot integration failed outright for those installs on
Windows (copilot.cmd not found), even though a working Copilot CLI was
present.

Fix

_copilot_executable() now probes PATH on Windows with shutil.which,
preferring copilot.exe, then copilot.cmd, then falls back to the
historical copilot.cmd default if nothing is found (so the resulting
"command not found" error still references the previously expected name).
Non-Windows behavior ("copilot") is unchanged. The env-var override
(SPECKIT_INTEGRATION_COPILOT_EXECUTABLE) still takes precedence, unchanged.

This mirrors the fix suggested in the issue report.

Test evidence

Added three regression tests in tests/integrations/test_extra_args.py
that monkeypatch os.name and shutil.which to simulate the three Windows
scenarios: only copilot.exe present, only copilot.cmd present, and
neither present.

Confirmed the key regression test fails without the fix
(git checkout HEAD~1 -- src/specify_cli/integrations/copilot/__init__.py,
then restored):

FAILED tests/integrations/test_extra_args.py::test_copilot_executable_windows_prefers_exe_on_path
AssertionError: assert 'copilot.cmd' == 'copilot.exe'

With the fix, all three new tests plus the existing Copilot executable/extra-args
tests pass:

$ .venv/bin/python -m pytest tests/integrations/test_extra_args.py -v -k copilot
...
8 passed, 30 deselected in 0.11s

Full suite (.venv/bin/python -m pytest): 10 failed, 8582 passed, 13 skipped.
The 10 failures are pre-existing *_python_parity tests (bash/PowerShell vs.
Python template-composition parity) unrelated to this change — confirmed by
running the same test files against the unmodified main branch, which
produces the identical 10 failures.

AI disclosure

Implemented autonomously by an OSS-contribution agent running Claude Code
(Claude Sonnet 5, claude-sonnet-5), operating without human line-by-line
review before this push. Extent: full code change and test authored by the
agent, based on the fix already suggested in the issue body; issue
investigation, prior-PR/claim search, and test verification (including the
before/after regression check) were also performed by the agent.

🤖 Generated with Claude Code

…ot.cmd

_copilot_executable() hardcoded the npm-shim name copilot.cmd on
Windows, so workflow command-step dispatch failed for any Copilot CLI
install that puts copilot.exe on PATH instead (standalone installer,
winget, scoop). Probe PATH for copilot.exe, then copilot.cmd, before
falling back to the historical default.

Fixes github#4755

Assisted-by: Claude Code (model: Claude Sonnet 5, autonomous)
@chelsealong
chelsealong requested a review from mnriem as a code owner September 25, 2026 17:36
@mnriem mnriem added the triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review label Sep 26, 2026
@mnriem
mnriem requested a balanced review from Copilot September 28, 2026 15:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The focused behavior change is well covered; only a minor stale docstring remains.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
What changed in this PR

Updates Windows Copilot CLI resolution to detect native and npm-installed executables.

Changes:

  • Probes PATH, preferring copilot.exe over copilot.cmd.
  • Adds regression coverage for executable detection and fallback behavior.

Regression evidence was provided but not independently rerun in this review environment.

File Description
src/​specify_cli/​integrations/​copilot/​__init__.py Adds Windows executable detection.
tests/​integrations/​test_extra_args.py Tests Windows detection and fallbacks.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/specify_cli/integrations/copilot/__init__.py Outdated
@chelsealong

Copy link
Copy Markdown
Contributor Author

Updated the _resolve_executable() docstring (lines 304-309) to describe the PATH-probing behavior instead of the stale copilot.cmd-always-on-Windows description. No behavior change; existing tests still pass (8 passed, 30 deselected).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Executable probing includes an unlaunchable bare-name branch, and existing Windows coverage remains stale and environment-dependent.

Review effort: Balanced
Findings: None

Resolved since last review (1)
Previously missed (3)

In code that hasn't changed since last review

Medium severity Resolve Copilot executable paths before subprocess launch

src/​specify_cli/​integrations/​copilot/​__init__.py:60

shutil.which("copilot") can succeed for a copilot.bat or .com through PATHEXT, but this returns the bare name. Unlike IntegrationBase.dispatch_command, the Copilot override passes that name directly to subprocess.run, and CreateProcess does not consult PATHEXT (src/specify_cli/integrations/base.py:449-454), so this branch detects an executable it cannot launch. Limit the probe to the two documented launchable names, or return the resolved path.

Medium severity Make Windows Copilot argument test deterministic

src/​specify_cli/​integrations/​copilot/​__init__.py:62

tests/test_workflows.py::test_copilot_exec_args still hardcodes copilot.cmd whenever os.name == "nt" (line 1030). With this new PATH-dependent result, that existing test fails on a Windows runner where copilot.exe is installed. Update it to mock shutil.which or derive the expectation from _copilot_executable() so the Windows test matrix remains deterministic.

Low severity Test executable preference when both Copilot variants are available

tests/​integrations/​test_extra_args.py:652

Despite the test name, this mock makes only copilot.exe available, so reversing the candidate order to prefer copilot.cmd would still pass all three new tests. Make both .exe and .cmd discoverable here while retaining the cmd-only test below to lock in the promised preference behavior.

@mnriem

mnriem commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback

…rministic

Copilot bot review flagged three issues: the bare "copilot" candidate in
_copilot_executable() can match a .bat/.com via PATHEXT that
CreateProcess can't actually launch; test_copilot_exec_args hardcoded
copilot.cmd for os.name == "nt" instead of deriving it from
_copilot_executable(), making it non-deterministic on a real Windows
runner; and the "prefers .exe" test only mocked .exe being present, so
it didn't actually lock in preference over .cmd.
@chelsealong

Copy link
Copy Markdown
Contributor Author

Addressed the three Copilot review findings from the latest pass:

  • Dropped the bare "copilot" PATH candidate — CreateProcess doesn't consult PATHEXT, so a match there (e.g. a .bat/.com) could never actually launch. Added a regression test (test_copilot_executable_windows_ignores_unlaunchable_bare_name) proving it now falls back to copilot.cmd instead.
  • tests/test_workflows.py::test_copilot_exec_args now derives its expectation from _copilot_executable() instead of hardcoding copilot.cmd for os.name == "nt", so it stays deterministic on a Windows runner regardless of what's actually on PATH.
  • test_copilot_executable_windows_prefers_exe_on_path now mocks both copilot.exe and copilot.cmd as present, so it actually locks in the .exe-over-.cmd preference rather than passing vacuously.

Full suite (.venv/bin/python -m pytest): 8593 passed, 13 skipped. uvx ruff@0.15.0 check src tests: all checks passed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The focused implementation addresses the reported regression with positive and negative coverage while preserving existing behavior.

Review effort: Balanced
Findings: None

@mnriem
mnriem merged commit 7c54ef5 into github:main Sep 28, 2026
15 checks passed
@mnriem

mnriem commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: Copilot integration hardcodes copilot.cmd, breaking installs that ship copilot.exe

3 participants