Skip to content

fix: Improve handling for streams that end without a finish reason - #47079

Open
amelvil2-ford wants to merge 2 commits into
anomalyco:devfrom
amelvil2-ford:no-finish-reason-fix
Open

fix: Improve handling for streams that end without a finish reason#47079
amelvil2-ford wants to merge 2 commits into
anomalyco:devfrom
amelvil2-ford:no-finish-reason-fix

Conversation

@amelvil2-ford

@amelvil2-ford amelvil2-ford commented Sep 3, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #45315

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

With this change, opencode will no longer end up in a loop when a stream ends without a finish reason

  • To be clear, a well behaving provider should always end its streams with a finish reason
  • This is meant as more of a reliability / fallback condition

I also added a plugin hook finish.chunk so that plugins that e.g. add providers can add custom handling if needed.

How did you verify your code works?

I tested it with my chaos testing openai compatible API that emits a stream that ends without a finish reason.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

If you do not follow this template your PR will be automatically rejected.

Sample plugin code

To try this out, add this to .opencode/plugins/main.ts

E.g. if you are running opencode in ~/someDirectory, this goes in someDirectory/.opencode/plugins/main.ts

import type { Hooks, Plugin, PluginInput } from "@opencode-ai/plugin"
export const LocalOpenAIPlugin : Plugin = async (pluginInput : PluginInput) : Promise<Hooks> => {
  const { project, client, $, directory, worktree } = pluginInput;

  return {
    "finish.chunk": async (input, output) => {
      output.ok = false
      output.message = `The stream ended with the finish reason "${input.finishReason}", but this sample plugin is setting all finish reasons to 'not ok'`
    },
  
  }
}

amelvil2 and others added 2 commits September 3, 2026 10:31
Added plugin hook finish.chunk so that plugins that e.g. add providers
can add custom handling if needed.
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Based on the search results, I found several potentially related PRs that address similar stream handling issues:

  1. fix(session): retry empty unknown-finish streams instead of stopping #43881 - fix(session): retry empty unknown-finish streams instead of stopping

    • Directly related: handles streams without finish reasons by retrying instead of stopping
  2. fix(ai): support streams without finish reasons #40965 - fix(ai): support streams without finish reasons

    • Directly related: adds support for streams without finish reasons
  3. fix(opencode): surface truncated turns instead of ending the loop #40142 - fix(opencode): surface truncated turns instead of ending the loop

    • Related: addresses infinite loop scenarios with stream handling
  4. fix(opencode): retry empty unknown responses #41466 - fix(opencode): retry empty unknown responses

    • Related: handles empty responses from streams
  5. fix(session): retry empty stream truncations and discard partial parts #26167 - fix(session): retry empty stream truncations and discard partial parts

    • Related: historical stream truncation handling

These PRs appear to address related concerns around stream termination and finish reason handling. PRs #43881 and #40965 are particularly relevant as they directly deal with streams ending without finish reasons. You may want to review these to ensure the new approach in PR #47079 doesn't duplicate existing logic or conflict with previous fixes.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Empty completions with an unknown finish reason retry without any bound

1 participant