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

  1. Ensure every JSON-RPC call in the batch sets either Request (method/params) or Error before calling pluginRPCRequests
  2. Fix the producing side (client or parser) so it never emits a call with both Request and Error nil
  3. Log the offending call's raw JSON to identify which entry is malformed
  4. 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

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


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, err

View on GitHub (pinned to 9f775e8a12)