anomalyco / anomalyco/opencode

core(v2): "unknown finish reason" error omits raw provider finish_reason

Open
#44,092 2 comments 1 reaction 1 assignee View on GitHub

@kitlangton is already working on this.

Since Aug 22, 2026.

2.0
Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Summary

In OpenCode V2, when a provider returns a non-standard finish_reason, the session step fails with:

Error: session.stream: The provider response ended with an unknown finish reason.
    at SessionRunner.runSteps
    at SessionRunner.drain

…without including the actual value the provider sent. V2 already captures that raw value internally — assistant messages are persisted with "finish":"error","rawFinish":"sensitive" — so the thrown error could surface it too. Today an opaque retry loop is indistinguishable from truncation/EOF/network issues; with the raw value included, this case is immediately recognizable as a provider-side content filter.

Environment

  • opencode version: opencode2 v0.0.0-beta-17898 (V2, beta channel)
  • OS: Linux 7.0.0-30-generic x86_64 (Ubuntu)
  • Terminal: ghostty (TERM=xterm-ghostty, COLORTERM=truecolor)
  • Shell: /usr/bin/zsh
  • Install/channel: beta
  • Active plugins: orchestator.ts, pushover-notifier.ts, rtk.ts (local), opencode-key-rotator (local), @slkiser/opencode-quota@latest, opencode-vibeguard@latest

Reproduction

  1. Use the built-in opencode provider with a free model whose operator applies server-side safety filtering. In my case opencode/x-preview-f-free — listed as "Ox Alpha Free (Unlimited)", a stealth reasoning preview model (release_date 2026-08-21) in the models.dev catalog used by OpenCode.
  2. Send prompts whose output trips the operator's sensitive-content filter.
  3. The stream gets cut mid-generation after partial reasoning/text output.

Intermittent by nature of provider filtering; in one V2 session it happened 24 times (49 automatic retries) across ~10 hours, ending in Failed to drain Session.

Expected Behavior

The error should include the unrecognized raw value so users can diagnose the cause, e.g.:

The provider response ended with an unknown finish reason: "sensitive"
Suggested Fix
  • In packages/opencode/src/session/llm/ai-sdk.ts, the adapter already reads event.rawFinishReason and special-cases "network_error" into a descriptive failure: ProviderError.ResponseStreamError("Provider finish_reason: network_error"). Applying the same pattern to unrecognized reasons — or simply appending the raw value to the "unknown finish reason" error — would close the observability gap.
  • Include the raw finish-reason string whenever it fails validation instead of discarding it.

Actual Behavior

Generic message with no indication of what the provider returned:

level=ERROR message="Failed to drain Session" cause="AI.Error: session.stream: The provider response ended with an unknown finish reason."

Notably, despite the throw, the partial content IS persisted along with the raw value — querying session storage (session_message) for that session shows:

SELECT json_extract(data,'$.finish'), json_extract(data,'$.rawFinish'), COUNT(*)
FROM session_message WHERE session_id='...' AND type='assistant'
GROUP BY 1,2;
-- error|sensitive|24
-- tool-calls|tool_calls|15
-- stop|stop|8
-- error||(empty)|4

So somewhere between stream ingestion and the thrown error the raw value is dropped, even though it demonstrably reaches persistence.

Additional Context

  • Related issues: #18813 (non-standard finish reason treated as unknown causes loop), #43622 / #41469 / #37852 (fallout of finish=unknown). None cover surfacing the raw value in the error itself.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.