wavetermdev/waveterm · error

response channel closed

Error message

response channel closed

What it means

WshRpc RPC callers wait on a per-request response channel. When the channel is closed (or delivers nil because the server shut down or the handler was finalized mid-request), the pending call cannot produce a response and returns "response channel closed". This signals the RPC was aborted, not that the remote command failed.

Source

Thrown at pkg/wshutil/wshrpc.go:602

			return true
		}
		handler.cachedResp = msg
		return false
	default:
		return false
	}
}

func (handler *RpcRequestHandler) NextResponse() (any, error) {
	var resp *RpcMessage
	if handler.cachedResp != nil {
		resp = handler.cachedResp
		handler.cachedResp = nil
	} else {
		resp = <-handler.respCh
	}
	if resp == nil {
		return nil, errors.New("response channel closed")
	}
	if resp.Error != "" {
		return nil, errors.New(resp.Error)
	}
	return resp.Data, nil
}

func (handler *RpcRequestHandler) finalize() {
	handler.callContextCancelFn()
	if handler.reqId != "" {
		handler.w.unregisterRpc(handler.reqId, nil)
	}
}

func (handler *RpcRequestHandler) callContextCancelFn() {
	cancelFnPtr := handler.ctxCancelFn.Swap(nil)
	if cancelFnPtr != nil && *cancelFnPtr != nil {
		(*cancelFnPtr)()

View on GitHub (pinned to a4447c1563)

Solutions

  1. Retry the request; transient shutdowns often resolve once the connection is re-established.
  2. Check server/connection state before issuing requests (w.IsServerDone()).
  3. Set a generous RpcOpts.Timeout so the call fails predictably instead of racing cleanup.
  4. Handle the error explicitly and surface it as 'connection closed' to callers.
  5. Ensure handler.finalize() isn't invoked early by your code paths.

Example fix

// before
respData, err := wshobj.SendComplexRequestTimeOut(ctx, "wave:path:list", opts, 5000)
// after
respData, err := wshobj.SendComplexRequestTimeOut(ctx, "wave:path:list", opts, 5000)
if err != nil {
    if err.Error() == "response channel closed" {
        respData, err = wshobj.SendComplexRequestTimeOut(ctx, "wave:path:list", opts, 5000) // retry after reconnect
    }
}
Defensive patterns

Strategy: retry

Validate before calling

if wshutil.(*wshrpc.WshRpc).IsServerDone() { skip/reconnect } // check via the client wrapper before calling

Type guard

func isRespChClosedErr(err error) bool { return err != nil && err.Error() == "response channel closed" }

Try / catch

data, err := handler.Wait()
if isRespChClosedErr(err) {
    data, err = retryWithBackoff(func() error { _, err := send(); return err })
}

Prevention

When it happens

Trigger: Calling SendComplexRequest/SendRequest and reading the result while the WshRpc server shuts down or the response handler is finalized/cleaned up before the response arrives (response channel closed, yielding nil).

Common situations: Client-side timeouts racing with late responses; connection drop closing the rpc channel; server process exit while a request is in flight.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/e158376d35618b4f. Report an issue: GitHub.