vet

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 11b64d2a21de6b36c0e2512cf9a5992110fce73b
parent e94d79c6bee96cb085236f1d0e49e0fd7da2d0c8
Author: Andrew Laack <andrew@laack.co>
Date:   Sun, 12 Apr 2026 17:40:02 -0500

fix: don't pass cwd to transport in opencode harness (#192)

The opencode client was passing cwd to both the subprocess Popen (via
transport options) and as --dir to the opencode CLI. When cwd is a
relative path like 'bouncer', Popen starts the process in bouncer/,
then opencode tries to resolve --dir bouncer relative to that, resulting
in bouncer/bouncer/ which doesn't exist. This caused a BrokenPipeError
because opencode exited immediately with 'Failed to change directory'.

Fix: remove cwd from transport options since --dir already tells
opencode where to work. Add a regression test verifying the transport
is built without cwd.

Co-authored-by: Agent Bot <agent-bot@openhost>
Diffstat:
Mvet/imbue_core/agents/agent_api/opencode/client.py | 4+---
Mvet/imbue_core/agents/agent_api/opencode/client_test.py | 46++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 47 insertions(+), 3 deletions(-)

diff --git a/vet/imbue_core/agents/agent_api/opencode/client.py b/vet/imbue_core/agents/agent_api/opencode/client.py @@ -34,9 +34,7 @@ class OpenCodeClient(RealAgentClient[OpenCodeOptions]): ) cmd = self._build_cli_cmd(self._options) - with AgentSubprocessCLITransport.build( - AgentSubprocessCLITransportOptions(cmd=cmd, cwd=self._options.cwd) - ) as transport: + with AgentSubprocessCLITransport.build(AgentSubprocessCLITransportOptions(cmd=cmd)) as transport: transport.write_stdin(prompt) for data in transport.receive_messages(): diff --git a/vet/imbue_core/agents/agent_api/opencode/client_test.py b/vet/imbue_core/agents/agent_api/opencode/client_test.py @@ -161,6 +161,52 @@ class TestProcessQuery: assert messages[0].error == "Rate limit exceeded" +class TestTransportOptions: + def test_transport_not_passed_cwd(self) -> None: + text_event = { + "type": "text", + "timestamp": 1, + "sessionID": "ses_test", + "part": { + "id": "prt_1", + "sessionID": "ses_test", + "messageID": "msg_1", + "type": "text", + "text": "hi", + }, + } + result_event = { + "type": "step_finish", + "timestamp": 2, + "sessionID": "ses_test", + "part": { + "id": "prt_2", + "sessionID": "ses_test", + "messageID": "msg_1", + "type": "step-finish", + "reason": "stop", + }, + } + + mock_transport = MagicMock() + mock_transport.receive_messages.return_value = iter([text_event, result_event]) + mock_transport.write_stdin = MagicMock() + mock_transport.__enter__ = MagicMock(return_value=mock_transport) + mock_transport.__exit__ = MagicMock(return_value=False) + + options = OpenCodeOptions(cli_path=Path("/usr/bin/opencode"), cwd="/my/project") + + with patch( + "vet.imbue_core.agents.agent_api.opencode.client.AgentSubprocessCLITransport.build", + return_value=mock_transport, + ) as mock_build: + client = OpenCodeClient(options) + list(client.process_query("test")) + + transport_options = mock_build.call_args[0][0] + assert transport_options.cwd is None + + class TestBuildContextManager: def test_build_yields_client(self) -> None: options = OpenCodeOptions(cli_path=Path("/usr/bin/opencode"))