anomalyco / anomalyco/opencode
`opencode run` (v2) exits 1 after a transient provider error that the runner retried and recovered from
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
We run opencode run non-interactively in automation with --format json. When the upstream model provider is flaky and a step fails mid-stream, opencode reports a provider.transport error (for example ECONNRESET: The socket connection was closed unexpectedly, with HTTP status 200 because the response headers had already arrived), the runner retries the step, and the session completes normally with finish reason stop. The process still exits with code 1. stderr is empty; the only trace is the earlier error event in the JSON stream.
Cause in the v2 runner, packages/cli/src/run/noninteractive.ts:
- The
session.step.failedhandler setsemittedError = trueandprocess.exitCode = 1, thencontinues:
https://github.com/anomalyco/opencode/blob/v2.0.10/packages/cli/src/run/noninteractive.ts#L470-L481
(permalink: https://github.com/anomalyco/opencode/blob/b8cedc1a7a5e2916bbb65dc1d4b620729c261638/packages/cli/src/run/noninteractive.ts#L470-L481) session.execution.succeededjust returns, so a later success never clears the code:
https://github.com/anomalyco/opencode/blob/v2.0.10/packages/cli/src/run/noninteractive.ts#L504
The file is identical from v2.0.0 through v2.0.10. In v1.18.31 the exit code is set only on startup errors, and a retried step does not affect it.
Observed: any non-interactive run in which one step failed transiently and was retried exits 1, even when the run finishes with reason stop and a complete answer.
Expected: exit 0 when the session completes successfully. The exit code should report the outcome of the run, not whether a recoverable fault happened along the way. Comparable tools (curl --retry, wget, pip, rsync) exit 0 when a retry succeeds and keep the fault in their logs.
Why it matters: anything scripting opencode run trusts the exit code. With this behavior a caller must either treat 1 as "maybe fine, parse the event stream" or discard good output.
Suggested fix: do not set process.exitCode = 1 on session.step.failed when the step is going to be retried, or clear it on session.execution.succeeded. If "completed, but errors were emitted" is worth surfacing, use a distinct documented exit code or an opt-in strict flag rather than 1.
Plugins
None.
OpenCode version
v2.0.10 (git tag; the v2 line is on the @opencode-ai/cli beta channel, 0.0.0-beta-*). Also v2.0.8. Not reproducible on v1.18.31 (opencode-ai@latest).
Steps to reproduce
- Run
opencode run --format json "<any multi-step prompt>"against a provider that drops one streaming response mid-body (a proxy that closes the socket after the headers and a few SSE events reproduces it; #39573 mentions a test-LLM harness that can inject a failure followed by successful replies). - Observe in the JSON stream: an
errorevent witherror.type == "provider.transport", followed by further steps and a finalstep_finishwith reasonstop. echo $?prints1.
Screenshot and/or share link
Not applicable (non-interactive; JSON event stream shows the error event followed by a normal finish).
Operating System
Linux (x86_64).
Terminal
Not applicable (headless).
Related: #39573 reports the same symptom class on the v1 runner (exit 1 after a recovered provider 413 during auto-compaction) through a different code path.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/cli/src/run/noninteractive.ts, reading the session.step.failed handler around lines 470-481 and session.execution.succeeded around line 504. Reproduce with opencode run --format json and a transient provider failure, then verify that a retried session ending with finish reason stop exits with code 0 while preserving the JSON error event.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100