From f98046c2fccaa7f9a24665a0d705a98006461da5 Mon Sep 17 00:00:00 2001 From: christopher-buss Date: Tue, 7 Oct 2025 17:18:16 +0100 Subject: [PATCH 1/2] fix: resolve executable path for cross-platform compatibility in CLI agent Resolves #276 --- clink/agents/base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/clink/agents/base.py b/clink/agents/base.py index fface9c..4d821c3 100644 --- a/clink/agents/base.py +++ b/clink/agents/base.py @@ -6,6 +6,7 @@ import asyncio import logging import os import shlex +import shutil import tempfile import time from collections.abc import Sequence @@ -65,6 +66,17 @@ class BaseCLIAgent: # The runner simply executes the configured CLI command for the selected role. command = self._build_command(role=role) env = self._build_environment() + + # Resolve executable path for cross-platform compatibility (especially Windows) + executable_name = command[0] + resolved_executable = shutil.which(executable_name) + if resolved_executable is None: + raise CLIAgentError( + f"Executable '{executable_name}' not found in PATH for CLI '{self.client.name}'. " + f"Ensure the command is installed and accessible." + ) + command[0] = resolved_executable + sanitized_command = list(command) cwd = str(self.client.working_dir) if self.client.working_dir else None From 4370be33b4b69a40456527213bcd62321a925a57 Mon Sep 17 00:00:00 2001 From: christopher-buss Date: Tue, 7 Oct 2025 17:39:41 +0100 Subject: [PATCH 2/2] test: fix clink agent tests to mock shutil.which() for executable resolution The previous commit (f98046c) added shutil.which() to resolve executables, which broke two tests that only mocked subprocess execution. This commit adds shutil.which() mocking to both test files to restore test compatibility. Co-authored-by: Claude --- tests/test_clink_codex_agent.py | 5 +++++ tests/test_clink_gemini_agent.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/tests/test_clink_codex_agent.py b/tests/test_clink_codex_agent.py index e788ea6..cdee3cd 100644 --- a/tests/test_clink_codex_agent.py +++ b/tests/test_clink_codex_agent.py @@ -1,4 +1,5 @@ import asyncio +import shutil from pathlib import Path import pytest @@ -41,7 +42,11 @@ async def _run_agent_with_process(monkeypatch, agent, role, process): async def fake_create_subprocess_exec(*_args, **_kwargs): return process + def fake_which(executable_name): + return f"/usr/bin/{executable_name}" + monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_create_subprocess_exec) + monkeypatch.setattr(shutil, "which", fake_which) return await agent.run(role=role, prompt="do something", files=[], images=[]) diff --git a/tests/test_clink_gemini_agent.py b/tests/test_clink_gemini_agent.py index e67ff2d..9165684 100644 --- a/tests/test_clink_gemini_agent.py +++ b/tests/test_clink_gemini_agent.py @@ -1,4 +1,5 @@ import asyncio +import shutil from pathlib import Path import pytest @@ -41,7 +42,11 @@ async def _run_agent_with_process(monkeypatch, agent, role, process): async def fake_create_subprocess_exec(*_args, **_kwargs): return process + def fake_which(executable_name): + return f"/usr/bin/{executable_name}" + monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_create_subprocess_exec) + monkeypatch.setattr(shutil, "which", fake_which) return await agent.run(role=role, prompt="do something", files=[], images=[])