{"record":{"id":"25e31c1d380c042d","repo":"vercel/ai","slug":"opencode-event-stream-ended-before-the-turn-settle","errorCode":null,"errorMessage":"OpenCode event stream ended before the turn settled.","messagePattern":"OpenCode event stream ended before the turn settled\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/harness-opencode/src/bridge/index.ts","lineNumber":776,"sourceCode":"  })();\n  const prompted = await legacySessionPrompt({\n    client,\n    sessionId,\n    start,\n  });\n  if (prompted.error) {\n    eventsAbort.abort();\n    turn.experimental_userMessages.close(\n      new Error(`OpenCode prompt failed: ${formatError(prompted.error)}`),\n    );\n    throw new Error(`OpenCode prompt failed: ${formatError(prompted.error)}`);\n  }\n  const settlement = await turnSettled.promise;\n  eventsAbort.abort();\n  await eventLoop.catch(() => {});\n  await userMessageLoop.catch(() => {});\n  if (settlement === 'stream-ended') {\n    throw new Error('OpenCode event stream ended before the turn settled.');\n  }\n  if (terminalError) throw new Error(terminalError);\n  if (!sawFinishStep) {\n    const emittedFallback = await emitContextFallback({\n      client,\n      sessionId,\n      assistantBaseline,\n      state,\n      emit,\n      emitContent: !sawContent,\n    }).catch(() => false);\n    if (!emittedFallback) {\n      throw new Error(\n        'OpenCode turn settled without a correlated assistant response.',\n      );\n    }\n  }\n  const finalSessionTokens =","sourceCodeStart":758,"sourceCodeEnd":794,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness-opencode/src/bridge/index.ts#L758-L794","documentation":"runPrompt listens to the OpenCode SSE event stream and waits on a deferred that resolves when the turn settles (idle/busy transition, error, or structured output). If the event stream itself ends first, the deferred resolves with 'stream-ended' and this error is thrown. It means the server closed the event connection before reporting any turn completion.","triggerScenarios":"The OpenCode SSE connection drops or the server closes it mid-turn (network interruption, server restart, idle timeout, proxy buffering/terminating long-lived connections), before a busy->idle transition or error event arrives.","commonSituations":"Reverse proxies (nginx default proxy_read_timeout) cutting long streaming requests; OpenCode server crash during a long tool loop; laptop sleep/network switch dropping the connection.","solutions":["Increase proxy idle/read timeouts for the OpenCode event endpoint (e.g. nginx proxy_read_timeout).","Check OpenCode server logs for a crash or restart during the turn.","Retry the turn; transient network drops are the most common cause.","Run the bridge and server on the same host/network to rule out intermediary connection termination."],"exampleFix":"// before: nginx default 60s read timeout kills long SSE turns\nlocation /event { proxy_pass http://opencode; }\n// after\nlocation /event { proxy_pass http://opencode; proxy_read_timeout 3600s; proxy_buffering off; }","handlingStrategy":"retry","validationCode":"null","typeGuard":"function isStreamEndedError(e: unknown): e is Error {\n  return e instanceof Error && e.message === 'OpenCode event stream ended before the turn settled.';\n}","tryCatchPattern":"try {\n  await bridge.runTurn(...);\n} catch (e) {\n  if (isStreamEndedError(e) && attempt < 3) {\n    await sleep(2 ** attempt * 1000);\n    return runTurnWithRetry(attempt + 1);\n  }\n  throw e;\n}","preventionTips":["Disable proxy buffering and raise read timeouts for SSE endpoints.","Keep the OpenCode server supervised to avoid mid-turn crashes.","Avoid running long turns over flaky networks/VPN.","Add automatic retry with backoff for this specific error."],"tags":["sse","streaming","network","opencode"],"backgroundTag":"stream-closed-early","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}