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

  1. Send exactly one done response: guard with handler.ResponseDone() or a local flag before the final SendResponse.
  2. Use SendResponseError as the single exit point for failure paths instead of responding in multiple places.
  3. Check handler.done / ResponseDone() before responding after cancellation or timeout.
  4. 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

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


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