Skip to content

fix(cli): report terminal outcomes for noninteractive runs - #60508

Open
rekram1-node wants to merge 1 commit into
devfrom
recovered-step-exit
Open

rekram1-node wants to merge 1 commit into
devfrom
recovered-step-exit

Conversation

@rekram1-node

@rekram1-node rekram1-node commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

opencode run --format=json can exit 1 and emit an error even when an interrupted model response recovers and the execution succeeds. Scripts and CI consumers cannot distinguish a recovered step from a failed run.

Fixes #50062.

Change

 failed model step
-  emit fatal JSON error and set exit 1
+  allow execution to continue
 execution becomes idle
-  treat any historical assistant error as fatal
+  use the terminal execution event or projected idle marker

Keep V1 behavior, real execution failures, SIGINT, and local cancellation exit codes. Wait for the terminal event when the idle marker has not projected yet, even if an assistant already responded. Non-question forms still fail non-interactive runs, now with an explicit diagnostic; question cancellation remains recoverable.

Related: #52885 addresses exit status. This also removes recovered-step JSON errors and uses execution markers, not the last assistant, when terminal events lag or are missing.

Out of scope

No model retry policy, Core/API changes, or new JSON event types. Step usage reconciliation is unchanged.

Verification

  • bun test test/run --timeout 30000 --rerun-each 10 --only-failures: 240 pass.
  • Full CLI bun test --timeout 30000 --only-failures: 364 pass, 9 skip, 0 fail across 54 files.
  • CLI bun typecheck: pass.
  • CLI bun run typecheck:tsc: pass.
  • Root bun run check: pass with existing lint warnings.
  • Regression coverage includes live/projected recovery, terminal failures after a clean assistant, delayed/missing terminal events, stream disconnection, interruption, form cancellation, and V1 behavior.

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72">
  <g fill="none" stroke="currentColor" opacity=".5">
    <rect x="4" y="16" width="62" height="36" rx="6"/>
    <path d="M66 34h20m-4-4 4 4-4 4"/>
    <rect x="86" y="16" width="66" height="36" rx="6"/>
    <path d="M152 34h18m-4-4 4 4-4 4"/>
    <rect x="170" y="16" width="66" height="36" rx="6"/>
  </g>
  <g fill="currentColor" font-family="monospace" font-size="10" text-anchor="middle">
    <text x="35" y="38">retry</text><text x="119" y="38">recover</text><text x="203" y="38">exit 0</text>
  </g>
  <circle cx="77" cy="34" r="2" fill="currentColor" opacity=".5">
    <animate attributeName="opacity" values=".2;1;.2" dur="2s" repeatCount="indefinite"/>
  </circle>
</svg>

Added by the opencode bot (AI-generated).

Related: #39573

Public API: opencode run: no error event or exit 1 for recovered steps; adds an explanatory error for cancelled non-question forms (event types unchanged, backwards compatible)

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Recovered runs now exit 0, and I found no blocking issues.

I ran the #50062 repro from source on both checkouts. On the base it emits a provider.transport error event and exits 1 even though the retried step finishes with stop. With this PR the same scenario has no error event and exits 0. One caveat: with the original 300 ms socket drop, the PR runs happened to drop before any output arrived, so core retried quietly. I delayed the drop to 1500 ms so the recovered-after-output path actually ran; the base still exits 1 under that timing. bun test test/run: 24 pass. tsc -b in packages/cli: no errors.

A step failure that core does not retry still ends in an execution failure, so real failures still exit 1. Interrupt (130), permission rejection and V1 behaviour are unchanged.

Public API: no SDK, HTTP, plugin or config changes, and no new event types. The changes are in what opencode run outputs and its exit codes: --format json no longer emits an error event for a recovered step (and default format no longer prints it to stderr); a cancelled form that isn't a question now emits one error event explaining why (it already exited 1); and a run only counts as successful if the execution itself succeeded, not just its last step. Consumers that parse error events are affected but won't break.

Related: #39573

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

opencode run (v2) exits 1 after a transient provider error that the runner retried and recovered from

1 participant