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
- Only call RPCBatchResponse when len(replies) > 0; otherwise fall back to RPCSingleResponse
- If the incoming batch is empty, return the protocol-defined error response instead of an empty batch
- 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
- Never call RPCBatchResponse with an empty slice; branch to a single error response
- Remember JSON-RPC 2.0 forbids empty batch arrays on both request and response side
- Assert non-empty batches in tests with at least one entry
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
- RPC message requires a response or notification
- RPC reply requires success or failure
- marshal box conf [ ] failed
- marshal master password migration failed
- 346
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)