{"record":{"id":"429294ccbd2a4fb6","repo":"router-for-me/CLIProxyAPI","slug":"wsrelay-connection-closed-during-response","errorCode":null,"errorMessage":"wsrelay: connection closed during response","messagePattern":"wsrelay: connection closed during response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/wsrelay/http.go","lineNumber":68,"sourceCode":"\t\tstreamResp *HTTPResponse\n\t\tstreamBody bytes.Buffer\n\t)\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, ctx.Err()\n\t\tcase msg, ok := <-respCh:\n\t\t\tif !ok {\n\t\t\t\tif streamMode {\n\t\t\t\t\tif streamResp == nil {\n\t\t\t\t\t\tstreamResp = &HTTPResponse{Status: http.StatusOK, Headers: make(http.Header)}\n\t\t\t\t\t} else if streamResp.Headers == nil {\n\t\t\t\t\t\tstreamResp.Headers = make(http.Header)\n\t\t\t\t\t}\n\t\t\t\t\tstreamResp.Body = append(streamResp.Body[:0], streamBody.Bytes()...)\n\t\t\t\t\treturn streamResp, nil\n\t\t\t\t}\n\t\t\t\treturn nil, errors.New(\"wsrelay: connection closed during response\")\n\t\t\t}\n\t\t\tswitch msg.Type {\n\t\t\tcase MessageTypeHTTPResp:\n\t\t\t\tresp := decodeResponse(msg.Payload)\n\t\t\t\tif streamMode && streamBody.Len() > 0 && len(resp.Body) == 0 {\n\t\t\t\t\tresp.Body = append(resp.Body[:0], streamBody.Bytes()...)\n\t\t\t\t}\n\t\t\t\treturn resp, nil\n\t\t\tcase MessageTypeError:\n\t\t\t\treturn nil, decodeError(msg.Payload)\n\t\t\tcase MessageTypeStreamStart, MessageTypeStreamChunk:\n\t\t\t\tif msg.Type == MessageTypeStreamStart {\n\t\t\t\t\tstreamMode = true\n\t\t\t\t\tstreamResp = decodeResponse(msg.Payload)\n\t\t\t\t\tif streamResp.Headers == nil {\n\t\t\t\t\t\tstreamResp.Headers = make(http.Header)\n\t\t\t\t\t}\n\t\t\t\t\tstreamBody.Reset()","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/wsrelay/http.go#L50-L86","documentation":"In internal/wsrelay/http.go the client relays an HTTP request over a websocket session and waits on respCh for the upstream answer. In non-stream mode, if the channel closes before any MessageTypeHTTPResp or MessageTypeError arrives (streamMode is false), it returns `wsrelay: connection closed during response`. This means the relay websocket terminated mid-flight — the peer went away, the session hit a deadline, or the relay session was dropped — so no response body will ever arrive.","triggerScenarios":"Non-streaming HTTP request relayed through internal/wsrelay where the relay websocket closes after the request is sent but before the MessageTypeHTTPResp frame arrives: upstream process crash/restart, wsrelay session deadline expiry (internal/wsrelay/session.go deadlines), network interruption between proxy and relay peer, or peer calling Close() during the round trip.","commonSituations":"Codex websocket relay deployments where the remote relay process restarts under load; long non-streaming generations outlasting the relay session lifetime; proxies/LB killing idle-looking websocket connections; flaky networks between the proxy host and the relay host.","solutions":["Retry the request: channel-closed is transient; a new relay session re-establishes the websocket.","Check wsrelay session deadline configuration in internal/wsrelay/session.go and raise it if non-streaming responses regularly outlast it.","Verify the relay peer process is alive and not OOM-killed/restarting (inspect logs around the failure time).","Enable websocket keepalive/ping on the relay path so intermediaries do not reap the connection.","Switch the call to streaming mode, which returns the buffered partial body instead of this hard error."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"func isRelayClosedDuringResponse(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"wsrelay: connection closed during response\")\n}","tryCatchPattern":"resp, err := relayClient.Do(ctx, req)\nif isRelayClosedDuringResponse(err) {\n    // idempotent retry with a fresh relay session; back off once first\n    time.Sleep(500 * time.Millisecond)\n    resp, err = relayClient.Do(ctx, req)\n}","preventionTips":["Size wsrelay session deadlines above your longest non-streaming generation.","Prefer streaming mode for long requests — it returns buffered partial data instead of failing hard.","Monitor relay peer liveness and restart it before sessions expire."],"tags":["websocket","wsrelay","network","connection-drop"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}