Repository navigation
Description
Summary
xAI Grok 4.6 Responses WebSocket drops mid-turn with close code 1006. OpenCode classifies this as provider.transport, SessionRunner.drain fails, and the TUI surfaces Error: WebSocket closed with code 1006 instead of retrying or falling back to HTTP.
Environment
- opencode version: 2.0.2
- OS: Darwin 24.6.0 (darwin arm64)
- Terminal: TERM_PROGRAM=ghostty, TERM=xterm-256color, COLORTERM=truecolor
- Shell: /opt/homebrew/bin/fish
- Install/channel: latest
- Active plugins:
@mohak34/opencode-notifier@latest(global); local TUI pluginherdr-tui-session.js(unrelated)
Reproduction
Intermittent. Observed three times in one evening on long xai/grok-4.6 sessions with many tool rounds.
- Use model
xai/grok-4.6(variantmedium) in the TUI against the shared background service. - Run a long agent turn with many tool calls (local shell / MCP). Context grows; xAI Responses WebSocket stays open for the session.
- Mid-step, the provider socket closes abnormally.
- TUI shows
Error: WebSocket closed with code 1006. The turn ends. Sending another prompt /continuecan resume later, but the in-flight step is lost.
Expected Behavior
A transient WebSocket 1006 during an in-flight xAI Responses stream should be treated as retryable transport (AI.Error.Transport, operation: "read"):
- If no assistant output has been committed, retry or fall back to HTTP (same idea as the SSE workaround / OpenAI WS retry path).
- If partial output already streamed, do not replay; surface a continue-after-partial-output path instead of killing drain.
- Docs currently say a closed socket reconnects on the next step and a failed open falls back to HTTP. Mid-stream close should not hard-fail
SessionRunner.drain.
Actual Behavior
Server log (role=server):
level=ERROR message="Failed to drain Session"
cause="AI.Error: WebSocket closed with code 1006
[cause]: AI.Error.Transport: WebSocket closed with code 1006
at SessionStep.attempt
at SessionRunner.runStep
at SessionRunner.drain"
Persisted assistant message:
{
"finish": "error",
"error": {
"type": "provider.transport",
"message": "WebSocket closed with code 1006"
},
"model": { "id": "grok-4.6", "providerID": "xai", "variant": "medium" }
}A sibling failure on the same session was stored as "message": "Session WebSocket closed" (provider.transport).
TUI displays the transport error verbatim. This is not the TUI losing its local OpenCode service WebSocket.
Observed in session_v2 (local DB, redacted paths):
| session | idle_outcome | error time (UTC) | message |
|---|---|---|---|
| ses_f692903d5ffe… | interrupted | 2026-09-12 18:22:14 | WebSocket closed with code 1006 |
| ses_f6922555dffe… | failed | 2026-09-12 18:26:14 | Session WebSocket closed |
| ses_f6922555dffe… | failed | 2026-09-12 18:31:20 | WebSocket closed with code 1006 |
Drops happened ~5–6 minutes into the session, so not xAI’s documented 25-minute socket cap. Same machine previously recorded xAI ECONNRESET / ENOTFOUND as provider.transport on grok-4.5.
Additional Context
- OpenCode v2 keeps one Responses WebSocket per session for OpenAI / xAI / Azure. xAI default
store: falsesends each step in full over the reused connection. - WebSocket 1006 = abnormal TCP close, no close frame (RST, proxy idle, peer drop). Cannot be sent as a close code.
- Related: OpenAI websocket disconnects before completion should retry safely #29646 (OpenAI WS 1006 should retry/HTTP-fallback; closed as not planned). OpenAI WebSocket 1009 retries the same oversized response.create five times before HTTP fallback #42572 / fix(core): fall back on oversized websocket requests v2 #43100 cover 1009 oversized-message HTTP fallback, which does not cover 1006.
- Local workaround that should work:
"providers": { "xai": { "websocket": false } }(not verified in this report). - User workaround: send
continueafter the error. Does not recover the failed step. - Project
opencode.jsonhad nowebsocketoverride (xAI WS left at built-in default). - Service was healthy (
opencode service status→http://127.0.0.1:49374).opencode --version= 2.0.2.
Suggested fix: classify mid-stream xAI/OpenAI WS close 1006 as retryable Transport before drain failure; HTTP-fallback when no output committed; reconnect-or-continue when output already streamed.
Activity
sankalpmukim commentedon Sep 12, 2026
Follow-up: not
@ai-sdk/xai; providerwebsocket: falseis ignored for GrokReproduced on 2.0.2 after setting
providers.xai.websocket: false. LiveGET /api/provider/xaireturned"websocket": false, then a new session still died with the same error.Babysit session
ses_f6906ee09ffeEfVdKX2PPKsh4o(xai/grok-4.6#medium, promptget to work): start 2026-09-12T18:55:00Z →provider.transportWebSocket closed with code 1006 at 18:58:30Z (~3.5 min, many tool/image rounds).idle_outcome=failed.Where the error is actually thrown
Exact message is OpenCode’s own transport, v2.0.2
packages/ai/src/route/transport/websocket.ts:That is
@opencode/aiWebSocketExecutoron the Responses WebSocket (wss://…/v1/responses), not a TUI↔server socket.@ai-sdk/xaiis not the chat WebSocketChecked
vercel/aipackages/xai(published@ai-sdk/[email protected]):fetchonly (OpenCode’sprovider-xai-responses.test.tsmocksfetchon.responses()).Why provider-level
websocket: falsedoes nothing on xAIXAIPluginstamps every xAI model (packages/core/src/plugin/provider/xai.ts):Catalog merge is
model.websocket ?? provider.websocket(packages/core/src/catalog.ts). Modeltruewins, so providerfalseis ignored.ModelResolverusesselected.websocket. Testenables xAI Responses WebSocketsexpectsgrok-4.6.websocket === true.ConfigProviderPluginruns later (post) but only writesmodel.websocketwhen set on the model. Same stamp exists on OpenAI models.Expected working config:
{ "providers": { "xai": { "websocket": false, "models": { "grok-4.6": { "websocket": false } } } } }Provider-only
falseis a documented no-op for Grok until the plugin stops forcing model-leveltrue, or merge treats providerfalseas an override.Still needed: treat mid-stream close 1006 as retryable
Transport(operation: "read") and HTTP-fallback when no output is committed.rekram1-node commentedon Sep 22, 2026
Fixed by #49183 and released in v2.0.5: acknowledged WebSocket read failures such as close code 1006 now retry before durable output and continue from durable history after partial output. #49350 (v2.0.6) also made provider-level HTTP transport overrides work, and #50031 (v2.0.11) pins a session to HTTP after repeated WebSocket stream losses. Closing as fixed.