Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions python/copilot/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import logging
import os
import re
import shutil
import subprocess
import sys
import threading
Expand Down Expand Up @@ -4328,11 +4327,15 @@ async def _start_cli_server(self) -> None:
cli_path = conn.path
assert cli_path is not None # resolved in __init__

# Verify CLI exists
# Verify the resolved CLI path exists. `cli_path` must already come from
# an explicit override, COPILOT_CLI_PATH, or the SDK-managed downloaded
# runtime (see `_resolve_runtime_entrypoint`); the SDK never falls back to
# searching PATH for an arbitrary system installation.
if not os.path.exists(cli_path):
original_path = cli_path
if (cli_path := shutil.which(cli_path)) is None:
raise RuntimeError(f"Copilot CLI not found at {original_path}")
raise RuntimeError(
f"Copilot CLI not found at {cli_path!r}. Set an explicit path, "
"COPILOT_CLI_PATH, or ensure the SDK-managed runtime download succeeded."
)

# Start with user-provided args, then add SDK-managed args
args = list(conn.args) + [
Expand Down
19 changes: 19 additions & 0 deletions python/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ def test_copilot_cli_path_does_not_require_runtime_bundle(tmp_path):
assert connection.path == str(explicit)


@pytest.mark.asyncio
async def test_missing_cli_path_does_not_fall_back_to_path_search(tmp_path):
"""A missing resolved CLI path must fail rather than silently resolving an
arbitrary same-named executable found on PATH (see #2524 / commit 05dd60e)."""
path_dir = tmp_path / "on_path"
path_dir.mkdir()
decoy = path_dir / "copilot"
decoy.write_text("#!/bin/sh\necho decoy\n")
decoy.chmod(0o755)

missing = tmp_path / "copilot"
connection = RuntimeConnection.for_stdio(path=str(missing))
client = CopilotClient(connection=connection, env={"PATH": str(path_dir)})

with patch.dict(os.environ, {"PATH": str(path_dir)}):
with pytest.raises(RuntimeError, match="Copilot CLI not found"):
await client._start_cli_server()


class TestBuiltinPluginDirectories:
@staticmethod
async def _start_client(paths=None):
Expand Down
Loading