router-for-me/CLIProxyAPI · error

wsrelay: connection closed during response

Error message

wsrelay: connection closed during response

What it means

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.

Source

Thrown at internal/wsrelay/http.go:68

		streamResp *HTTPResponse
		streamBody bytes.Buffer
	)
	for {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case msg, ok := <-respCh:
			if !ok {
				if streamMode {
					if streamResp == nil {
						streamResp = &HTTPResponse{Status: http.StatusOK, Headers: make(http.Header)}
					} else if streamResp.Headers == nil {
						streamResp.Headers = make(http.Header)
					}
					streamResp.Body = append(streamResp.Body[:0], streamBody.Bytes()...)
					return streamResp, nil
				}
				return nil, errors.New("wsrelay: connection closed during response")
			}
			switch msg.Type {
			case MessageTypeHTTPResp:
				resp := decodeResponse(msg.Payload)
				if streamMode && streamBody.Len() > 0 && len(resp.Body) == 0 {
					resp.Body = append(resp.Body[:0], streamBody.Bytes()...)
				}
				return resp, nil
			case MessageTypeError:
				return nil, decodeError(msg.Payload)
			case MessageTypeStreamStart, MessageTypeStreamChunk:
				if msg.Type == MessageTypeStreamStart {
					streamMode = true
					streamResp = decodeResponse(msg.Payload)
					if streamResp.Headers == nil {
						streamResp.Headers = make(http.Header)
					}
					streamBody.Reset()

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Retry the request: channel-closed is transient; a new relay session re-establishes the websocket.
  2. Check wsrelay session deadline configuration in internal/wsrelay/session.go and raise it if non-streaming responses regularly outlast it.
  3. Verify the relay peer process is alive and not OOM-killed/restarting (inspect logs around the failure time).
  4. Enable websocket keepalive/ping on the relay path so intermediaries do not reap the connection.
  5. Switch the call to streaming mode, which returns the buffered partial body instead of this hard error.
Defensive patterns

Strategy: retry

Type guard

func isRelayClosedDuringResponse(err error) bool {
    return err != nil && strings.Contains(err.Error(), "wsrelay: connection closed during response")
}

Try / catch

resp, err := relayClient.Do(ctx, req)
if isRelayClosedDuringResponse(err) {
    // idempotent retry with a fresh relay session; back off once first
    time.Sleep(500 * time.Millisecond)
    resp, err = relayClient.Do(ctx, req)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/429294ccbd2a4fb6. Report an issue: GitHub.