siyuan-note/siyuan · error
RPC call requires a request or error
Error message
RPC call requires a request or error
What it means
When converting an incoming JSON-RPC batch/plugin call into the internal processing-request list, each call must carry either a Request or an Error object. If a call has neither (and no error was already assigned), pluginRPCRequests rejects it with this message because a JSON-RPC call with no request payload and no error cannot be dispatched.
Solutions
- Ensure every JSON-RPC call in the batch sets either Request (method/params) or Error before calling pluginRPCRequests
- Fix the producing side (client or parser) so it never emits a call with both Request and Error nil
- Log the offending call's raw JSON to identify which entry is malformed
- Add a validation step on parsed calls before dispatch
Example fix
// before
calls := []JsonRpcCall{{ID: json.RawMessage(`1`)}}
reqs, err := pluginRPCRequests(calls)
// after
calls := []JsonRpcCall{{ID: json.RawMessage(`1`), Request: &JsonRpcCallRequest{Method: "siyuan.someMethod"}}}
reqs, err := pluginRPCRequests(calls) Defensive patterns
Strategy: validation
Validate before calling
for i, call := range calls {
if call.Request == nil && call.Error == nil {
return fmt.Errorf("call %d has neither request nor error", i)
}
} Type guard
func hasPayload(c JsonRpcCall) bool { return c.Request != nil || c.Error != nil } Prevention
- Always populate Request or Error when building JsonRpcCall structs
- Validate parsed JSON-RPC batches before dispatch
- Fuzz the parser with malformed batch entries in tests
When it happens
Trigger: Passing a JsonRpcCall (or batch element) to pluginRPCRequests where both Call.Request is nil and no Error was set — e.g. a hand-constructed call struct, a batch entry that only carried an ID, or a parser that dropped an empty request object.
Common situations: Building RPC call structs manually in tests or plugins; third-party clients sending malformed JSON-RPC batch entries like {"jsonrpc":"2.0","id":1} with no method/params; version drift between the caller's serialization and the rpc_contract parser.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- RPC reply requires a response or error
- first argument must be method name string
- invalid id field: must be string, number, null or omitted
- invalid id field: must be string, number, null or omitted
- invalid jsonrpc version
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/979b8bb963757f11.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/plugin/rpc_contract.go:114
}
func pluginRPCRequests(calls []apicontract.PluginRPCParsedCall) ([]*JsonRpcProcessingRequest, error) {
requests := make([]*JsonRpcProcessingRequest, len(calls))
for i, call := range calls {
if call.Error != nil {
data, err := json.Marshal(call.Error)
if err != nil {
return nil, err
}
var failure JsonRpcErrorResponse
if err := json.Unmarshal(data, &failure); err != nil {
return nil, err
}
requests[i] = &JsonRpcProcessingRequest{Error: &failure}
continue
}
if call.Request == nil {
return nil, fmt.Errorf("RPC call requires a request or error")
}
request := &JsonRpcRequest{JsonRpc: JsonRpcVersion, Method: call.Request.Method}
request.Params.Exists, request.ID.Exists = call.Request.ParamsPresent, call.Request.IDPresent
// 只有 RPC 的参数和关联标识进入脚本运行时的动态值,方法和请求结构保持类型约束。
params, err := json.Marshal(call.Request.Params)
if err != nil {
return nil, err
}
if err := json.Unmarshal(params, &request.Params.Value); err != nil {
return nil, err
}
request.Params.IsNull = call.Request.ParamsNull
id, err := json.Marshal(call.Request.ID)
if err != nil {
return nil, err
}
if err := json.Unmarshal(id, &request.ID.Value); err != nil {
return nil, errView on GitHub (pinned to 9f775e8a12)