diff --git a/python/copilot/client.py b/python/copilot/client.py index ab3410a56c..e7026cab32 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -20,7 +20,6 @@ import logging import os import re -import shutil import subprocess import sys import threading @@ -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) + [ diff --git a/python/test_client.py b/python/test_client.py index e62154e247..fff506ec99 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -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):