{"record":{"id":"e158376d35618b4f","repo":"wavetermdev/waveterm","slug":"response-channel-closed","errorCode":null,"errorMessage":"response channel closed","messagePattern":"response channel closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/wshutil/wshrpc.go","lineNumber":602,"sourceCode":"\t\t\treturn true\n\t\t}\n\t\thandler.cachedResp = msg\n\t\treturn false\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (handler *RpcRequestHandler) NextResponse() (any, error) {\n\tvar resp *RpcMessage\n\tif handler.cachedResp != nil {\n\t\tresp = handler.cachedResp\n\t\thandler.cachedResp = nil\n\t} else {\n\t\tresp = <-handler.respCh\n\t}\n\tif resp == nil {\n\t\treturn nil, errors.New(\"response channel closed\")\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, errors.New(resp.Error)\n\t}\n\treturn resp.Data, nil\n}\n\nfunc (handler *RpcRequestHandler) finalize() {\n\thandler.callContextCancelFn()\n\tif handler.reqId != \"\" {\n\t\thandler.w.unregisterRpc(handler.reqId, nil)\n\t}\n}\n\nfunc (handler *RpcRequestHandler) callContextCancelFn() {\n\tcancelFnPtr := handler.ctxCancelFn.Swap(nil)\n\tif cancelFnPtr != nil && *cancelFnPtr != nil {\n\t\t(*cancelFnPtr)()","sourceCodeStart":584,"sourceCodeEnd":620,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/wshutil/wshrpc.go#L584-L620","documentation":"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.","triggerScenarios":"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).","commonSituations":"Client-side timeouts racing with late responses; connection drop closing the rpc channel; server process exit while a request is in flight.","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."],"exampleFix":"// before\nrespData, err := wshobj.SendComplexRequestTimeOut(ctx, \"wave:path:list\", opts, 5000)\n// after\nrespData, err := wshobj.SendComplexRequestTimeOut(ctx, \"wave:path:list\", opts, 5000)\nif err != nil {\n    if err.Error() == \"response channel closed\" {\n        respData, err = wshobj.SendComplexRequestTimeOut(ctx, \"wave:path:list\", opts, 5000) // retry after reconnect\n    }\n}","handlingStrategy":"retry","validationCode":"if wshutil.(*wshrpc.WshRpc).IsServerDone() { skip/reconnect } // check via the client wrapper before calling","typeGuard":"func isRespChClosedErr(err error) bool { return err != nil && err.Error() == \"response channel closed\" }","tryCatchPattern":"data, err := handler.Wait()\nif isRespChClosedErr(err) {\n    data, err = retryWithBackoff(func() error { _, err := send(); return err })\n}","preventionTips":["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."],"tags":["go","rpc","channel"],"backgroundTag":"rpc-response-channel-closed","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}