Skip to content
Open
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
12 changes: 10 additions & 2 deletions envs/opencode_env/harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,16 @@ def create(
task: Any,
seed: int | None = None,
episode_id: str | None = None,
start_agent: bool = True,
) -> OpenCodeSession:
"""Create one session, retrying with exponential backoff.

Session creation spins up a sandbox, installs opencode, and starts the
proxy + agent, the flakiest step in a rollout. Each failed attempt tears
its own sandbox down (see :meth:`_create_once`), so a retry never leaks.

Pass ``start_agent=False`` to return before launching the agent (for
example to run setup first), then call ``session.start_agent()``.
"""
import logging
import time
Expand All @@ -239,7 +243,9 @@ def create(
last_exc: Exception = RuntimeError("create_attempts must be >= 1")
for i in range(self._create_attempts):
try:
return self._create_once(task, seed=seed, episode_id=episode_id)
return self._create_once(
task, seed=seed, episode_id=episode_id, start_agent=start_agent
)
except Exception as exc: # noqa: BLE001
last_exc = exc
if i + 1 < self._create_attempts:
Expand All @@ -256,6 +262,7 @@ def _create_once(
task: Any,
seed: int | None = None,
episode_id: str | None = None,
start_agent: bool = True,
) -> OpenCodeSession:
import logging
_log = logging.getLogger(__name__)
Expand Down Expand Up @@ -318,7 +325,8 @@ def _create_once(
proxy_trace_path=proxy_trace_path,
proxy_bg_job=proxy_bg_job,
)
session.start_agent()
if start_agent:
session.start_agent()
return session
except Exception as exc:
_log.error("factory.create: setup failed, killing sandbox: %r", exc)
Expand Down
1 change: 1 addition & 0 deletions envs/opencode_env/opencode_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def build_run_cmd(config: OpenCodeConfig) -> str:

format_flag = "--format json" if config.run_format == "json" else ""
return (
"set -o pipefail && "
'export PATH="$HOME/.opencode/bin:$PATH" && '
f"cd {workdir_path(config)} && "
f'opencode run {format_flag} "$(cat {instruction_path(config)})" '
Expand Down
20 changes: 9 additions & 11 deletions envs/opencode_env/server/opencode_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,21 +333,15 @@ def _emit(msg: str) -> None:
f"creating E2B sandbox (template={template or 'default'}) — "
"this is the slow phase (~5–60s cold, ~5s with template)"
)
session = factory.create(task=opencode_task)
session = factory.create(task=opencode_task, start_agent=False)
result.sandbox_id = session.sandbox.sandbox_id
_emit(
f"sandbox ready: {result.sandbox_id} — agent started "
f"sandbox ready: {result.sandbox_id} "
f"({'proxy on :7000, logprobs capturing' if mode == 'transparent_proxy' else 'direct LLM, no logprobs'})"
)

# Run setup commands one at a time, *before* the agent starts.
# The factory has already started the agent in start_agent()
# during create(); to keep the order "setup → agent → verify"
# we'd need to restructure. As a pragmatic compromise we run
# setup IMMEDIATELY after create(), which races with the agent
# for ~1-2s but is fine for typical pip/git/download work
# because opencode itself takes >=20s to make its first model
# call.
# Run setup one command at a time, before the agent starts. create()
# was called with start_agent=False, so the agent has not launched yet.
for i, cmd in enumerate(setup, 1):
_emit(f"setup [{i}/{len(setup)}]: {cmd[:80]}")
cr = self._exec_command(session.sandbox, cmd, timeout=SETUP_TIMEOUT_S)
Expand All @@ -359,8 +353,9 @@ def _emit(msg: str) -> None:
_emit(f"setup FAILED at [{i}]: exit={cr.exit_code}")
break

# Block until the agent is done (or setup already failed).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timeouts ignore sequential setup cost

Medium Severity

Deferring the agent until after setup makes wall-clock time setup + agent instead of overlapping them, but sandbox lifetime stays agent_timeout_s + 300 and the MCP tool cap stays 900s. With default agent_timeout_s=600 and SETUP_TIMEOUT_S=300, longer setups can kill the sandbox or trip the tool timeout before the agent finishes.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e7aadc8. Configure here.

# Setup is done and clean, so launch the agent now (deferred from create()).
if result.error is None:
session.start_agent()
_emit(
f"agent running — opencode CLI in sandbox "
f"(timeout {int(agent_timeout_s)}s)"
Expand All @@ -370,6 +365,9 @@ def _emit(msg: str) -> None:
timeout_s=agent_timeout_s
)
_emit(f"agent finished: exit_code={result.agent_exit_code}")
if result.agent_exit_code != 0:
result.error = f"agent exited non-zero ({result.agent_exit_code})"
_emit(f"agent FAILED: exit_code={result.agent_exit_code}")
Comment thread
cursor[bot] marked this conversation as resolved.
except TimeoutError as exc:
result.error = f"agent timeout: {exc}"
_emit(f"agent TIMEOUT: {exc}")
Expand Down
4 changes: 2 additions & 2 deletions tests/envs/test_opencode_factory_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def test_create_retries_then_succeeds(monkeypatch):
calls = {"n": 0}
session = object()

def _flaky(task, seed=None, episode_id=None):
def _flaky(task, seed=None, episode_id=None, start_agent=True):
calls["n"] += 1
if calls["n"] < 3:
raise RuntimeError("transient create failure")
Expand All @@ -97,7 +97,7 @@ def test_create_raises_after_exhausting_attempts(monkeypatch):
factory = _factory(sandbox, create_attempts=3, create_backoff_s=0)
calls = {"n": 0}

def _always_fails(task, seed=None, episode_id=None):
def _always_fails(task, seed=None, episode_id=None, start_agent=True):
calls["n"] += 1
raise RuntimeError("persistent create failure")

Expand Down
Loading