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

  1. Set the field to exactly the string "2.0" (case-sensitive, no whitespace)
  2. Ensure the client serializes it as a JSON string, not a number
  3. 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

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.

Related errors


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)