Skip to content

[python] [Bug] Windows: subprocess.Popen fails to find copilot CLI - WinError 2 #79

@petrroll

Description

@petrroll

Environment

  • OS: Windows 11
  • Python: 3.12.10
  • Package: github-copilot-sdk v0.1.14
  • Copilot CLI Version: 0.0.388

Description

On Windows, the CopilotClient fails to start with FileNotFoundError: [WinError 2] The system cannot find the file specified when the copilot CLI is installed as a .cmd or .bat file (which is standard for npm global installs on Windows).

Steps to Reproduce

  1. Install copilot CLI via npm on Windows: npm install -g @anthropic/copilot-cli
  2. Verify it's on PATH: where copilot returns C:\.tools\.npm\prefix\copilot.cmd
  3. Use the Python SDK:
from copilot import CopilotClient

async def main():
    client = CopilotClient()
    await client.start()  # Raises WinError 2

Expected Behavior

The SDK should find and execute the copilot CLI when it's on PATH, regardless of file extension.

Actual Behavior

FileNotFoundError: [WinError 2] The system cannot find the file specified

Full traceback:

File "...\copilot\client.py", line 645, in _start_cli_server
    self._process = subprocess.Popen(
                    ^^^^^^^^^^^^^^^^^
File "...\subprocess.py", line 1026, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
File "...\subprocess.py", line 1538, in _execute_child
    hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [WinError 2] The system cannot find the file specified

NOTE: Bellow is generated by copilot, so who even knows. Also for some reason it worked for a bit then it stopped and weirdly enough my VSCode has some arcane issues with path which it (maybe) passed down on it's child processes started from its terminal. So maybe it's all a big misunderstanding.


Root Cause

In client.py line ~645, subprocess.Popen is called with the command name directly:

self._process = subprocess.Popen(
    args,  # args[0] is "copilot" or cli_path
    ...
)

On Windows, subprocess.Popen without shell=True:

  1. Does not use PATHEXT to resolve extensions (.cmd, .bat, .exe)
  2. Cannot directly execute .cmd/.bat files by name alone
  3. Does not behave like a shell when searching PATH

Workaround

Users can work around this by using shutil.which() to resolve the full path:

import shutil
from copilot import CopilotClient

cli_path = shutil.which("copilot")  # Returns full path with extension
client = CopilotClient({"cli_path": cli_path})
await client.start()  # Works

Suggested Fix

In client.py, before calling subprocess.Popen, use shutil.which() to resolve the CLI path:

import shutil

async def _start_cli_server(self) -> None:
    cli_path = self.options["cli_path"]
    
    # Resolve the full path on Windows (handles .cmd/.bat files)
    resolved_path = shutil.which(cli_path)
    if resolved_path:
        cli_path = resolved_path
    
    args = ["--server", "--log-level", self.options["log_level"]]
    # ... rest of the code

Alternatively, on Windows, use shell=True when the cli_path doesn't contain a path separator (indicating it needs PATH resolution).

Additional Context

This affects any Windows user where copilot is installed via npm, as npm creates .cmd wrapper scripts for global packages.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions