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
- Retry the request; transient shutdowns often resolve once the connection is re-established.
- Check server/connection state before issuing requests (w.IsServerDone()).
- Set a generous RpcOpts.Timeout so the call fails predictably instead of racing cleanup.
- Handle the error explicitly and surface it as 'connection closed' to callers.
- 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
- Match server lifetime to request lifetime; don't send after Close().
- Use RpcOpts.Timeout larger than expected handler duration.
- Avoid calling finalize() while a request is in flight.
- Treat this error as transport-level and reconnect.
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
- nil wshrpc passed to wshclient
- no default route
- <resp.Error>
- server is no longer running, cannot send new requests
- no route id available
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/e158376d35618b4f.
Report an issue: GitHub.