siyuan-note/siyuan · error
invalid jsonrpc version
Error message
invalid jsonrpc version: %s
What it means
The "jsonrpc" member is present but its value is not exactly the string "2.0". JSON-RPC 2.0 requires this exact version string; anything else ("1.0", "2.00", 2.0 as a number, etc.) is rejected as an Invalid Request and converted to a -32600 error reply.
Solutions
- Set the field to exactly the string "2.0" (case-sensitive, no whitespace)
- Ensure the client serializes it as a JSON string, not a number
- Check for trailing whitespace or encoding issues in the payload generator
Example fix
// before
{"jsonrpc":"1.0","method":"plugin/invoke","id":1}
// after
{"jsonrpc":"2.0","method":"plugin/invoke","id":1} Defensive patterns
Strategy: validation
Validate before calling
var probe struct{ JsonRpc *string `json:"jsonrpc"` }
if json.Unmarshal(payload, &probe) != nil || probe.JsonRpc == nil || *probe.JsonRpc != "2.0" { return errors.New("jsonrpc must be the string \"2.0\"") } Try / catch
if call.Error != nil && call.Error.Error != nil && call.Error.Error.Code == -32600 {
log.Printf("request rejected: %s", call.Error.Error.Message)
} Prevention
- Define a shared constant for the version string and interpolate it, never type "2.0" inline per call
- Serialize the version as a quoted JSON string, not a number
- Migrate clients off JSON-RPC 1.x before switching endpoints
When it happens
Trigger: DecodePluginRPC parses a request whose jsonrpc field is "1.0", "1.1", "2.0 " (whitespace), or a JSON number 2.0 instead of the string "2.0".
Common situations: Clients written for the legacy JSON-RPC 1.0 protocol, serializers that emit 2.0 unquoted, version string built with format specifiers adding whitespace, or copy-pasted spec examples from 1.x docs.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- invalid jsonrpc version
- invalid id field: must be string, number, null or omitted
- missing jsonrpc field
- missing method field
- RPC call requires a request or error
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/bf25c597ab1bb912.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/plugin_rpc.go:182
return json.Unmarshal(data, &o.Value)
}
func parsePluginRPCCall(data []byte) PluginRPCParsedCall {
type JsonRpcRequestObject struct {
JsonRpc rpcOptional[string] `json:"jsonrpc"`
Method rpcOptional[string] `json:"method"`
Params rpcOptional[JSONValue] `json:"params"`
ID rpcOptional[JSONValue] `json:"id"`
}
var fields JsonRpcRequestObject
err := json.Unmarshal(data, &fields)
var id PluginRPCID
if err == nil {
switch {
case !fields.JsonRpc.Exists:
err = fmt.Errorf("missing jsonrpc field")
case fields.JsonRpc.Value != "2.0":
err = fmt.Errorf("invalid jsonrpc version: %s", fields.JsonRpc.Value)
case !fields.Method.Exists || fields.Method.IsNull:
err = fmt.Errorf("missing method field")
default:
if fields.ID.Exists && !fields.ID.IsNull {
raw, _ := json.Marshal(fields.ID.Value)
err = json.Unmarshal(raw, &id)
}
}
}
if err != nil {
message := strings.ReplaceAll(err.Error(), "apicontract.JsonRpcRequestObject", "plugin.JsonRpcRequestObject")
failure := RPCErrorResponse(-32600, "Invalid Request", "RPC request is not a valid JSON-RPC object: "+message)
return PluginRPCParsedCall{Error: &failure}
}
return PluginRPCParsedCall{Request: &PluginRPCCall{Method: fields.Method.Value, Params: fields.Params.Value, ParamsPresent: fields.Params.Exists, ParamsNull: fields.Params.IsNull, ID: id, IDPresent: fields.ID.Exists}}
}
// RPC 请求保留批量中的单项结构错误,参数形状错误仍交给调用分发处理通知语义。View on GitHub (pinned to 9f775e8a12)