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:
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"))