siyuan-note/siyuan · error

RPC batch response must not be empty

Error message

RPC batch response must not be empty

What it means

PluginRPCResponse represents either a single reply or a JSON-RPC batch (array) response, selected by RPCSingleResponse() or RPCBatchResponse(). MarshalJSON throws when neither is set, or when RPCBatchResponse was called with an empty or nil slice: a batch response with zero entries is invalid per JSON-RPC 2.0 (an empty request array must yield a single error response, not an empty one).

Solutions

  1. Only call RPCBatchResponse when len(replies) > 0; otherwise fall back to RPCSingleResponse
  2. If the incoming batch is empty, return the protocol-defined error response instead of an empty batch
  3. Guard marshal sites by checking that the response was constructed with one of the two builders

Example fix

// before
var replies []apicontract.PluginRPCReply
return apicontract.RPCBatchResponse(replies)
// after
if len(replies) == 0 {
    return apicontract.RPCSingleResponse(apicontract.RPCFailureReply(
        apicontract.RPCErrorResponse(-32600, "Invalid Request", "empty batch")))
}
return apicontract.RPCBatchResponse(replies)
Defensive patterns

Strategy: validation

Validate before calling

if len(replies) == 0 { return apicontract.RPCSingleResponse(apicontract.RPCFailureReply(
    apicontract.RPCErrorResponse(-32600, "Invalid Request", "empty batch"))) }

Try / catch

data, err := json.Marshal(resp)
if err != nil {
    resp = apicontract.RPCSingleResponse(apicontract.RPCFailureReply(
        apicontract.RPCErrorResponse(-32603, "Internal error", err.Error())))
    data, err = json.Marshal(resp)
}

Prevention

When it happens

Trigger: Marshaling a zero PluginRPCResponse, or calling RPCBatchResponse(nil)/RPCBatchResponse([]PluginRPCResponse{}{}) and serializing the result.

Common situations: Building replies in a loop that never executes (empty request batch handled elsewhere), accumulating batch replies into a nil slice, or default-constructing the response type before deciding on single vs batch.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/ce072343adede6f2. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/plugin_rpc.go:115

}

type PluginRPCResponse struct {
	single *PluginRPCReply
	batch  []PluginRPCReply
}

func RPCSingleResponse(reply PluginRPCReply) PluginRPCResponse {
	return PluginRPCResponse{single: &reply}
}
func RPCBatchResponse(replies []PluginRPCReply) PluginRPCResponse {
	return PluginRPCResponse{batch: replies}
}
func (r PluginRPCResponse) MarshalJSON() ([]byte, error) {
	if r.single != nil {
		return json.Marshal(r.single)
	}
	if len(r.batch) == 0 {
		return nil, fmt.Errorf("RPC batch response must not be empty")
	}
	return json.Marshal(r.batch)
}

type PluginRPCCall struct {
	Method        string
	Params        JSONValue
	ParamsPresent bool
	ParamsNull    bool
	ID            PluginRPCID
	IDPresent     bool
}

type PluginRPCParsedCall struct {
	Request *PluginRPCCall
	Error   *PluginRPCFailure
}

View on GitHub (pinned to 9f775e8a12)