siyuan-note/siyuan · error

invalid RPC ID

Error message

invalid RPC ID

What it means

PluginRPCID wraps the JSON-RPC-style "id" field of plugin RPC requests. Its UnmarshalJSON first requires the raw value to be valid, non-empty JSON; otherwise it returns "invalid RPC ID". This catches syntactically broken or empty id payloads before the per-type member check (string/number/null).

Solutions

  1. Send the id as a proper JSON value: a quoted string, a number, or null (or omit the field where allowed).
  2. Use JSON.stringify / json.Marshal on the whole message instead of manual string building.
  3. Validate the message body with a JSON parser before posting it to the RPC endpoint.

Example fix

// before
{"jsonrpc":"2.0","id":undefined,"method":"x"} // -> {"id":} invalid
// after
{"jsonrpc":"2.0","id":"req-1","method":"x"}
Defensive patterns

Strategy: try-catch

Validate before calling

function checkRpcId(id) { if (!(typeof id === "string" || typeof id === "number" || id === null)) throw new Error("rpc id must be string, number or null"); }

Type guard

const isValidRpcId = (id) => typeof id === "string" || (typeof id === "number" && Number.isFinite(id)) || id === null;

Try / catch

try { const parsed = JSON.parse(msg); send(parsed); } catch (e) { if (String(e).includes("invalid RPC ID")) { regenerateAndResendWithScalarId(); } else { throw e; } }

Prevention

When it happens

Trigger: Unmarshalling a plugin RPC message whose id field is invalid JSON (e.g. raw bytes like `undefined`, a bare token, or truncated data), or an empty/whitespace-only value.

Common situations: A plugin builds the JSON-RPC message by string concatenation and emits a non-JSON id (undefined in JS serializes oddly or is dropped); truncated transport frames; hand-written test fixtures with invalid JSON.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — 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/3dc5f11c246da878. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/plugin_rpc.go:17

package apicontract

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"strings"
)

type PluginRPCID struct{ value JSONValue }

func (id PluginRPCID) MarshalJSON() ([]byte, error) { return json.Marshal(id.value) }
func (id *PluginRPCID) UnmarshalJSON(data []byte) error {
	data = bytes.TrimSpace(data)
	if !json.Valid(data) || len(data) == 0 {
		return fmt.Errorf("invalid RPC ID")
	}
	if data[0] != '"' && data[0] != '-' && (data[0] < '0' || data[0] > '9') && string(data) != "null" {
		return fmt.Errorf("invalid id field: must be string, number, null or omitted")
	}
	return json.Unmarshal(data, &id.value)
}

type PluginRPCParams struct{ value JSONValue }

type PluginRPCRequestFields struct {
	JSONRPC string           `json:"jsonrpc" api:"const=\"2.0\""`
	Method  string           `json:"method"`
	Params  *PluginRPCParams `json:"params" api:"optional"`
	ID      *PluginRPCID     `json:"id" api:"optional"`
}

type PluginRPCSuccess struct {
	JSONRPC string      `json:"jsonrpc" api:"const=\"2.0\""`

View on GitHub (pinned to 9f775e8a12)