You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
session: auto-continue after a stream interruption is delivered as an unmarked synthetic user message #56295
When a response stream ends without finish_reason, OpenCode injects the synthetic user message "The previous response was interrupted. Continue from where you left off without repeating completed content." and continues the turn. That message reaches the model as plain user-role text with no mark that it was generated automatically, so the model cannot distinguish it from actual user input and can treat it as new user intent.
In my case the stream was cut right after the model had asked me to approve a plan. The model took the continuation as my approval and posted GitHub comments and pushed commits before I had replied with GO. Its reasoning states that the user had said "GO"; there is no GO in the actual user messages.
This auto-continue occurred 36 times in about six weeks across recent 2.0.x builds in my setup. Most are handled as ordinary continuations; the damage happens when the cut coincides with an approval wait.
Proposal: mark this synthetic user message so the model can tell it is system-generated and not user input. For example, add a [synthetic]-style prefix to the text, or deliver it through the existing system-update path (developer on Responses, <system-update> on Chat protocols). It must stay functional as a continuation instruction.
Related: #41358 (auto-continue after compaction), #54180 / #55076 (automatic resume after restart/kill), #41866 (a synthetic user instruction that overrode review-only scope). The triggers are different from this one.
Plugins
Local plugins are active, but this is core session-runner behavior and does not depend on a plugin.
OpenCode version
2.0.26
Steps to reproduce
Have the model output a message asking the user to approve a change (waiting for GO).
Before the turn completes, the response stream ends without finish_reason (a transient provider-side failure; in my case OpenCode Go / deepseek-v4.1-flash (OpenAI Chat compatible), recorded as provider.invalid-output: "OpenAI Chat stream ended without finish_reason").
The harness injects the continuation message.
The model interprets it as the user's approval and acts before the approval arrives.
Reproduction is probabilistic; the cut has to coincide with the approval wait.
Thanks for the detailed report. I reproduced this on 2.0.26, and it still happens on the latest dev (512165c).
Setup: the simulated OpenAI Chat model streams "Plan: push the fix and comment on the issue. Reply GO to approve." and then the connection drops before any finish_reason. OpenCode retries, and the next request to the model ends with:
user: Fix the bug, but wait for my GO before pushing.
assistant: Plan: push the fix and comment on the issue. Reply GO to approve.
user: The previous response was interrupted. Continue from where you left off without repeating completed content.
The continuation goes out as a plain user message with nothing marking it as automatic. Right after an approval request, it looks like the user replying. All synthetic messages are converted this way in packages/core/src/session/runner/to-llm-message.ts, while system messages already go through the <system-update> / developer path.
One limit: I cut the stream with a dropped connection rather than your exact "stream ended without finish_reason" error. Both lead to the same continuation code. The simulated model also can't show a real model taking this as approval; the test only shows that nothing marks the message as automatic.
The recording and screenshot below show the run, and the script below reproduces it.
recording.mp4
Reproduction script (drive.ts)
// Repro for anomalyco/opencode#56295: after an OpenAI Chat stream ends without// finish_reason, OpenCode auto-continues by sending a plain user-role message,// indistinguishable from real user input.import{Effect,Stream}from"effect"import{Llm,OpenCodeDriver}from"opencode-drive"constrequests: Array<unknown>=[]exportdefaultOpenCodeDriver.use({
...(process.env.OC_DEV ? {opencode: {dev: process.env.OC_DEV}} : {}),tui: {recording: true},},({ ui, llm })=>Effect.gen(function*(){yield*llm.serve((request: any,index)=>{requests.push(request.body)if(index===0)// Ask for approval, then cut the stream before any finish_reason arrives.returnStream.make(Llm.text("Plan: push the fix and comment on the issue. Reply GO to approve."),Llm.disconnect(),)returnStream.make(Llm.text("Continuing."))})yield*ui.submit("Fix the bug, but wait for my GO before pushing.")yield*ui.waitFor("Continuing.",{timeout: 30000}).pipe(Effect.catchTag("UiWaitTimeoutError",(e: any)=>Effect.sync(()=>console.log("TIMEOUT; requests:",requests.length,JSON.stringify(e.frame?.lines?.map((l: any)=>l.text??JSON.stringify(l)).join("\n")).slice(0,4000))),),)yield*ui.screenshot("after-continue")constlast: any=requests[requests.length-1]constmsgs=last?.messages??[]console.log("REQUEST COUNT",requests.length)console.log("LAST REQUEST MESSAGES (tail):")console.log(JSON.stringify(msgs.slice(-3),null,2))}),)
Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.
Activity
opencode-agent commentedon Oct 17, 2026
Thanks for the detailed report. I reproduced this on 2.0.26, and it still happens on the latest
dev(512165c).Setup: the simulated OpenAI Chat model streams "Plan: push the fix and comment on the issue. Reply GO to approve." and then the connection drops before any
finish_reason. OpenCode retries, and the next request to the model ends with:The continuation goes out as a plain user message with nothing marking it as automatic. Right after an approval request, it looks like the user replying. All synthetic messages are converted this way in
packages/core/src/session/runner/to-llm-message.ts, while system messages already go through the<system-update>/ developer path.One limit: I cut the stream with a dropped connection rather than your exact "stream ended without finish_reason" error. Both lead to the same continuation code. The simulated model also can't show a real model taking this as approval; the test only shows that nothing marks the message as automatic.
The recording and screenshot below show the run, and the script below reproduces it.
recording.mp4
Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.