siyuan-note/siyuan · error

left document version is required

Error message

left document version is required

What it means

The /api/history/diffDocVersions decoder requires a `left` field containing a document version reference object. It unmarshals fields["left"] into a map; if unmarshaling fails or the result is nil (missing, null, or non-object), it returns 'left document version is required'. Both left and right sides are mandatory for a diff.

Solutions

  1. Send "left" as an object like {"type": "id", "id": "<block-id>", "path": "<history-path>"}
  2. Ensure left is a JSON object, not a quoted string
  3. Check both left and right are present — the right side is validated next with a parallel message

Example fix

// before
{"right": {"type": "id", "id": "20240101120000-abcdef1"}}
// after
{"left": {"type": "id", "id": "20240101110000-abcdef0"}, "right": {"type": "id", "id": "20240101120000-abcdef1"}}
Defensive patterns

Strategy: validation

Validate before calling

function isValidVersionRef(v) {
  return v != null && typeof v === "object" && !Array.isArray(v) && typeof v.type === "string" && v.type.trim() !== "";
}
if (!isValidVersionRef(body.left)) throw new Error("left version ref required");

Type guard

const isVersionRef = (v) => v !== null && typeof v === "object" && typeof v.type === "string";

Prevention

When it happens

Trigger: POSTing to /api/history/diffDocVersions with no `left` key, "left": null, a non-object left (string/number/array), or malformed JSON in left.

Common situations: Diff UI invoking the endpoint when only one history version is selected; clients double-encoding the version object as a string.

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/d26baabc8d6371ac. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/history_diff.go:52

type DocVersionDiffResult struct {
	Left          *DocVersionDiffContent  `json:"left"`
	Right         *DocVersionDiffContent  `json:"right"`
	Differences   []*DocVersionDifference `json:"differences"`
	Large         bool                    `json:"large"`
	Fallback      bool                    `json:"fallback"`
	Message       string                  `json:"message"`
	TitleModified bool                    `json:"titleModified"`
}

func init() {
	DiffDocVersions.decodeRequest = func(reader io.Reader) (request DiffDocVersionsRequest, err error) {
		fields, err := blockRequestFields(reader, "/api/history/diffDocVersions")
		if err != nil {
			return request, err
		}
		var left, right map[string]json.RawMessage
		if json.Unmarshal(fields["left"], &left) != nil || left == nil {
			return request, fmt.Errorf("left document version is required")
		}
		if json.Unmarshal(fields["right"], &right) != nil || right == nil {
			return request, fmt.Errorf("right document version is required")
		}
		if request.Left, err = decodeDocVersionRef(left); err != nil {
			return request, err
		}
		request.Right, err = decodeDocVersionRef(right)
		return request, err
	}
}

func decodeDocVersionRef(fields map[string]json.RawMessage) (request DocVersionRef, err error) {
	if request.Type, err = legacyField[string](fields, "type", "String", true); err != nil {
		return request, err
	}
	request.Type = strings.TrimSpace(request.Type)
	if request.Type == "" {

View on GitHub (pinned to 9f775e8a12)