Skip to content

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

Closed

Description

@nicolehaugen

Bug Description

CopilotIntegration._resolve_executable() falls back to a hardcoded, platform-guessed executable name on Windows (copilot.cmd) instead of detecting what's actually installed. When the GitHub Copilot CLI is installed in a way that produces copilot.exe rather than an npm-style .cmd shim, workflow/command dispatch to the copilot integration fails outright on Windows, because Spec Kit never looks for the actual executable on PATH.

Steps to Reproduce

  1. On Windows, install the Copilot CLI via a distribution that produces copilot.exe on PATH (not an npm global-install shim producing copilot.cmd).
  2. Run any Spec Kit workflow/command step with integration: copilot (e.g. specify workflow run ...).
  3. Dispatch fails because Specify attempts to invoke copilot.cmd, which does not exist, instead of the copilot.exe that is actually on PATH.

Expected Behavior

Spec Kit should detect the actual Copilot CLI executable available on the user's PATH on Windows, rather than assuming a single fixed name.

Actual Behavior

Specify attempts to invoke copilot.cmd even when only copilot.exe is installed on PATH, so workflow/command dispatch fails.

Specify CLI Version

1.0.7 (as encountered; likely affects other versions using the same _copilot_executable() logic)

AI Agent

GitHub Copilot

Operating System

Windows (version not specified)

Python Version

N/A (not provided)

Error Logs

N/A (no specific error output provided)

Additional Context

Where: src/specify_cli/integrations/copilot/__init__.py

def _copilot_executable() -> str:
    """Return the executable name for Copilot CLI on this platform.

    On Windows, subprocess invocation is reliable with `copilot.cmd`.
    """
    if os.name == "nt":
        return "copilot.cmd"
    return "copilot"
def _resolve_executable(self) -> str:
    """Return the Copilot CLI executable, respecting the env-var override.

    Checks ``SPECKIT_INTEGRATION_COPILOT_EXECUTABLE`` first.  Falls
    back to the platform-specific default from ``_copilot_executable()``
    (``copilot.cmd`` on Windows, ``copilot`` elsewhere) so that
    existing behaviour is preserved when the env var is unset.
    """
    env_name = "SPECKIT_INTEGRATION_COPILOT_EXECUTABLE"
    override = os.environ.get(env_name, "").strip()
    return override if override else _copilot_executable()

Why this likely wasn't caught earlier:

  • Workflow command-step dispatch is a newer code path. Most integration scaffolding (init, command registration, writing .github//.claude/ files) never has to spawn the Copilot CLI as a subprocess. _resolve_executable()/build_exec_args() only matter once something actually shells out to copilot, which is essentially exclusive to specify workflow run command steps — so long-time Spec Kit + Copilot users could easily never exercise this path.
  • The hardcoded copilot.cmd guess suggests testing only covered an npm global install (npm install -g @github/copilot or similar), which npm wraps in a .cmd shim on Windows by convention. Installing the Copilot CLI via a different channel (standalone installer, winget, scoop, a signed native binary, etc.) produces a real copilot.exe on PATH instead, and hits this bug immediately and reliably. If most Windows testers install via npm, this simply never surfaces for them.
  • The SPECKIT_INTEGRATION_COPILOT_EXECUTABLE override itself appears recently added (per its own docstring: "so that existing behaviour is preserved when the env var is unset"), suggesting this is a known, actively-being-patched rough edge rather than a long-stable path.

Net: this only surfaces at the intersection of (a) workflows that dispatch to Copilot as a subprocess, (b) Windows, and (c) a non-npm Copilot CLI install — a narrow combination most contributors/testers may not sit in.

Suggested fix:

def _copilot_executable() -> str:
    """Return the executable name for Copilot CLI on this platform."""
    if os.name != "nt":
        return "copilot"

    for candidate in ("copilot.exe", "copilot.cmd", "copilot"):
        if shutil.which(candidate):
            return candidate

    # Nothing found on PATH — keep the historical default so the
    # resulting "command not found" error still references the
    # previously expected name.
    return "copilot.cmd"

shutil is already imported in this module, so this requires no new dependency. Suggest adding a regression test alongside the existing _resolve_executable/build_exec_args coverage in tests/integrations/test_extra_args.py, mocking shutil.which to simulate a Windows install exposing only copilot.exe.

Workaround: SPECKIT_INTEGRATION_COPILOT_EXECUTABLE already exists and can be set to copilot.exe explicitly, scoped to the invoking process, until this is fixed upstream.

AI Disclosure

GitHub Copilot (GPT-6 Sol) organized the user-provided issue text into the repository's bug-report template and filed it. Human-directed; no independent reproduction or code changes. Reasoning-effort setting not provided.

Activity

  1. github-actions commented on Sep 25, 2026

    @github-actions
    Contributor

    Bug assessment — windows-copilot-executable: Valid · severity medium


    Bug Assessment: Windows Copilot integration hardcodes copilot.cmd

    Report (summarized)

    The reporter states that on Windows, Spec Kit's Copilot integration always selects copilot.cmd. That works for an npm-generated command shim but fails when the Copilot CLI is installed through a distribution that places a native copilot.exe on PATH. The failure occurs when a workflow or command step dispatches to Copilot as a subprocess. The report identifies Spec Kit 1.0.7, Windows (version unspecified), and provides an environment-variable workaround: SPECKIT_INTEGRATION_COPILOT_EXECUTABLE=copilot.exe.

    There are no issue comments adding reproduction detail or context, and no additional URL was supplied.

    Symptom

    On Windows, a Copilot workflow/command dispatch attempts to execute copilot.cmd even when only copilot.exe is available on PATH, causing process startup to fail. The expected behavior is to resolve an installed Copilot executable rather than assume the npm shim name.

    Reproduction

    1. On Windows, install the Copilot CLI from a distribution that exposes copilot.exe on PATH without a copilot.cmd shim.
    2. Configure or invoke a Spec Kit workflow command step using integration: copilot.
    3. Observe that dispatch constructs an invocation beginning with copilot.cmd, so execution fails if that file is absent.

    [NEEDS CLARIFICATION: The issue does not include the exact process-start error, Windows version, Copilot distribution/version, or a captured PATH.]

    Suspected Code Paths

    • src/specify_cli/integrations/copilot/__init__.py:49-56 — _copilot_executable() returns the fixed string copilot.cmd whenever os.name == "nt"; it does not query PATH.
    • src/specify_cli/integrations/copilot/__init__.py:292-302 — CopilotIntegration._resolve_executable() honors SPECKIT_INTEGRATION_COPILOT_EXECUTABLE, but otherwise delegates directly to the fixed platform default.
    • src/specify_cli/integrations/copilot/__init__.py:304-326 — build_exec_args() places the resolved executable at argv[0] for non-interactive Copilot execution.
    • src/specify_cli/integrations/copilot/__init__.py:391-400 — dispatch_command() also uses _resolve_executable() when constructing workflow/command subprocess arguments, so the same Windows default affects the reported path.
    • tests/integrations/test_extra_args.py:632-639 — existing coverage asserts only that the unset executable uses _copilot_executable(); it does not verify Windows PATH discovery or the .exe case.

    Root Cause Hypothesis

    Confidence: high. The current Windows branch intentionally returns copilot.cmd without checking whether that command exists or whether another supported executable is available. Both the regular argument builder and command dispatch consume that value, which directly explains the reported failure for native .exe installations. The environment override mitigates the issue for users who know to set it but does not provide automatic detection.

    Proposed Remediation

    Preferred: Make the Windows executable resolver check PATH with shutil.which() for supported forms in an explicit order, such as copilot.exe, copilot.cmd, and then copilot. Preserve the existing non-Windows result (copilot) and retain the historical copilot.cmd fallback when no candidate is found, so missing-install diagnostics do not unexpectedly change. Keep SPECKIT_INTEGRATION_COPILOT_EXECUTABLE as the highest-priority explicit override. Update the resolver docstring to describe discovery rather than a platform guess.

    Alternatives (optional):

    • Return copilot on Windows and rely entirely on Windows command resolution. This is simpler, but may be less predictable for subprocess invocation and could change behavior for existing npm-shim users.
    • Require the explicit executable environment variable on Windows. This avoids guessing but is a usability regression and does not satisfy automatic discovery.

    Files likely to change:

    • src/specify_cli/integrations/copilot/__init__.py
    • tests/integrations/test_extra_args.py (or tests/integrations/test_integration_copilot.py)

    Tests to add or update:

    • Mock Windows platform detection and shutil.which() to verify that a PATH-only copilot.exe resolves to copilot.exe.
    • Verify candidate precedence when both .exe and .cmd are present.
    • Verify the historical fallback when no candidate is found.
    • Verify the explicit executable environment variable still takes precedence over PATH discovery.
    • Verify both build_exec_args() and dispatch_command() use the discovered executable.

    Risks & Considerations

    • Candidate ordering is an observable behavior; document and test it so installations with multiple Copilot forms are deterministic.
    • shutil.which() depends on the process environment, so PATH changes after integration construction could affect later dispatch; resolving immediately before command construction is appropriate.
    • The fallback should remain a clear failure path when Copilot is not installed; do not silently return an invalid or empty executable.
    • No data migration or API change is expected. The change is limited to executable selection on Windows, but it may alter which installed Copilot artifact is invoked when multiple artifacts coexist.

    Open Questions

    • [NEEDS CLARIFICATION: Which Copilot CLI distribution(s) and Windows versions produce the failing copilot.exe-only setup?]
    • [NEEDS CLARIFICATION: Should .exe, .cmd, or another command form have precedence when more than one is present on PATH?]
    • [NEEDS CLARIFICATION: What exact subprocess error is emitted in the affected environment?]

    Generated by 🐛 Assess Bug from Labeled Issue for #4755 · copilot · gpt52codex · 1.39 AIC · ⌖ 5.65 AIC · ⊞ 15.6K · ◷

  2. added
    triage-nice-to-haveVerdict: evidence-backed fix or greenlit feature — land after review
    bug-fixTrigger the bug-fix agentic workflow
    on Sep 26, 2026
  3. github-actions commented on Sep 28, 2026

    @github-actions
    Contributor

    Pull request created: #4770

    Generated by Fix Bug from Labeled Issue · copilot · gpt52codex · 1.96 AIC · ⌖ 7.21 AIC · ⊞ 17K

  4. added a commit that references this issue on Sep 28, 2026
    7c54ef5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug-assessbug-fixTrigger the bug-fix agentic workflowseverity-mediumtriage-nice-to-haveVerdict: evidence-backed fix or greenlit feature — land after review

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions