wavetermdev/waveterm · error
request already done, cannot send additional response
Error message
request already done, cannot send additional response
What it means
RpcResponseHandler.SendResponse guards with an atomic done flag: once a request has been finalized (a done response sent, cancelled, or timed out), further SendResponse calls are rejected with this error to prevent double responses or writes to unregistered requests.
Source
Thrown at pkg/wshutil/wshrpc.go:686
Command: wshrpc.Command_Message,
Data: wshrpc.CommandMessageData{
Message: msg,
},
Route: handler.source, // send back to source
}
msgBytes, _ := json.Marshal(rpcMsg) // will never fail
select {
case handler.w.OutputCh <- msgBytes:
case <-handler.ctx.Done():
}
}
func (handler *RpcResponseHandler) SendResponse(data any, done bool) error {
defer func() {
panichandler.PanicHandler("SendResponse", recover())
}()
if handler.done.Load() {
return fmt.Errorf("request already done, cannot send additional response")
}
if done {
defer handler.close()
}
if handler.reqId == "" {
return nil
}
msg := &RpcMessage{
ResId: handler.reqId,
Data: data,
Cont: !done,
}
barr, err := json.Marshal(msg)
if err != nil {
return err
}
select {
case handler.w.OutputCh <- barr:View on GitHub (pinned to a4447c1563)
Solutions
- Send exactly one done response: guard with handler.ResponseDone() or a local flag before the final SendResponse.
- Use SendResponseError as the single exit point for failure paths instead of responding in multiple places.
- Check handler.done / ResponseDone() before responding after cancellation or timeout.
- Restructure streaming handlers so the final done packet is sent in one defer/one place.
Example fix
// before
handler.SendResponse(chunk1, false)
handler.SendResponse(chunk2, true)
if err != nil { handler.SendResponseError(err) } // second done response
// after
handler.SendResponse(chunk1, false)
if err != nil { handler.SendResponseError(err); return }
handler.SendResponse(chunk2, true) Defensive patterns
Strategy: try-catch
Validate before calling
if handler.ResponseDone() {
return fmt.Errorf("skip: response already sent for req %s", handler.reqId)
} Try / catch
if err := handler.SendResponse(data, done); err != nil && strings.Contains(err.Error(), "already done") {
// a done response/cancel/timeout won the race; log and stop sending further responses
return nil
} Prevention
- Send exactly one done=true response per request handler.
- Centralize the final response in a single code path (or defer).
- Check ResponseDone()/done before responding after cancellation or timeout.
- In streaming handlers, send data with done=false and reserve done=true for the final chunk only.
When it happens
Trigger: Calling SendResponse twice on the same handler; calling SendResponse after SendResponseError already sent a done response; responding after the request was cancelled/timed out; streaming code continuing to push data after sending the final done=true packet.
Common situations: Streaming RPC handlers with multiple return paths that each send a final response; error paths that both log-and-respond and fall through to a second respond; cancel/timeout racing with normal completion.
Related errors
- Layout model not found
- Display container not found
- call ${methodName} error: ${respData.error}
- rpc command "${msg.command}" not supported by [${this.routeI
- msg.error
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/5dc0d238aa62c2cc.
Report an issue: GitHub.