Skip to content

fix: resolve silent agent termination on stream drop#9589

Closed
Varuu-0 wants to merge 4 commits intoKilo-Org:mainfrom
Varuu-0:fix/silent-stream-interruption
Closed

fix: resolve silent agent termination on stream drop#9589
Varuu-0 wants to merge 4 commits intoKilo-Org:mainfrom
Varuu-0:fix/silent-stream-interruption

Conversation

@Varuu-0
Copy link
Copy Markdown
Contributor

@Varuu-0 Varuu-0 commented Apr 28, 2026

Context

This PR addresses a critical bug where Kilo agents would prematurely stop generating text mid-response without any error shown to the user. This silent failure occurs when the upstream Server-Sent Events (SSE) connection drops unexpectedly before a proper terminal event or finish_reason is received from the provider. Previously, the system defaulted to an "other" finish reason, which the agent loop interpreted as a successful completion, skipping the error handling and retry logic entirely.

fixes

#8476
#9544

Implementation

To fix this, the system must properly distinguish between a clean generation completion and a premature stream termination:

  1. Provider Tracking: Updated openai-compatible-chat-language-model.ts and openai-responses-language-model.ts to actively track whether a terminal finish chunk or reason was successfully received. If the stream flushes prematurely, they now emit an unknown finish reason instead of defaulting to other.
  2. Mapper Updates: Modified map-openai-compatible-finish-reason.ts and map-openai-responses-finish-reason.ts to map unrecognized states to unknown instead of other.
  3. Triggering Retries: Updated the session processor (processor.ts) to intercept the unknown finish reason immediately after the stream drains. If no tool parts were executed, it throws a retryable MessageV2.APIError ("Stream terminated prematurely without a finish reason"), which triggers the existing Effect.retry pipeline.
  4. Defense in Depth: Modified the agent loop condition in prompt.ts to explicitly exclude other as a valid completion signal, ensuring no ambiguous states can cause a silent loop exit.

Screenshots

File Before After
openai-compatible-chat-language-model.ts typescript<br/>let finishReason = {<br/> unified: "other",<br/> raw: undefined<br/>}<br/> typescript<br/>let finishReason = { ... }<br/>let receivedFinishReason = false;<br/><br/>/* in flush() */<br/>if (!receivedFinishReason && !isFirstChunk) {<br/> finishReason = { unified: "unknown", raw: undefined }<br/>}<br/>
openai-responses-language-model.ts typescript<br/>let finishReason = {<br/> unified: "other",<br/> raw: undefined<br/>}<br/> typescript<br/>let finishReason = { ... }<br/>let receivedFinishChunk = false;<br/><br/>/* in flush() */<br/>if (!receivedFinishChunk && responseId !== null) {<br/> finishReason = { unified: "unknown", raw: undefined }<br/>}<br/>
processor.ts typescript<br/>yield* stream.pipe(<br/> Stream.tap(...),<br/> Stream.takeUntil(...),<br/> Stream.runDrain<br/>)<br/><br/><br/> typescript<br/>yield* stream.pipe(...)<br/><br/>if (ctx.assistantMessage.finish === "unknown") {<br/> const parts = MessageV2.parts(ctx.assistantMessage.id)<br/> if (!parts.some(p => p.type === "tool")) {<br/> yield* Effect.fail(new MessageV2.APIError({<br/> message: "Stream terminated prematurely...",<br/> isRetryable: true<br/> }).toObject())<br/> }<br/>}<br/>
prompt.ts typescript<br/>const finished = handle.message.finish &&<br/> !["tool-calls", "unknown"].includes(handle.message.finish)<br/> typescript<br/>const finished = handle.message.finish &&<br/> !["tool-calls", "unknown", "other"].includes(handle.message.finish)<br/>
Mappers typescript<br/>default:<br/> return "other"<br/> typescript<br/>default:<br/> return "unknown"<br/>

How to Test

  1. Start an agent generation that produces a long response.
  2. Artificially simulate a network drop or prematurely kill the provider stream before it sends the final finish_reason chunk.
  3. Observe that instead of the agent stopping its execution with a half-written response, it catches the interruption, logs a retryable API Error, and automatically retries the generation.

Get in Touch

Discord @varuu

Comment thread packages/opencode/src/session/processor.ts Outdated
@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot Bot commented Apr 28, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (7 files)
  • packages/opencode/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts
  • packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts
  • packages/opencode/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts
  • packages/opencode/src/provider/sdk/copilot/responses/openai-responses-language-model.ts
  • packages/opencode/src/session/message-v2.ts
  • packages/opencode/src/session/processor.ts
  • packages/opencode/src/session/prompt.ts

Reviewed by gpt-5.5-20260423 · 1,392,325 tokens

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 313b3ba3c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/opencode/src/session/processor.ts Outdated
Comment thread packages/opencode/src/session/prompt.ts Outdated
@Varuu-0 Varuu-0 marked this pull request as draft April 28, 2026 19:54
@Varuu-0 Varuu-0 closed this Apr 28, 2026
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.

1 participant