siyuan-note/siyuan · error

Field [transactions] must not be empty

Error message

Field [transactions] must not be empty

What it means

The performTransactions endpoint requires a non-empty `transactions` array. The legacy field decoder demands the key be present as an Array, and a present-but-empty array is explicitly rejected because a transaction batch with nothing in it has no meaning and would silently do nothing.

Solutions

  1. Include at least one transaction object with actions in the transactions array
  2. Skip calling the endpoint entirely when the collected operations list is empty
  3. Inspect the client op-queue logic to find why it produced zero operations
  4. Check that operations are not being filtered out (e.g. by dedup/undo logic) before submit

Example fix

// before
fetchPost("/api/transaction", {transactions: [], reqId: 1})
// after
if (ops.length > 0) {
  fetchPost("/api/transaction", {transactions: [{doOperations: ops, undoOperations: undo}], reqId: 1})
}
Defensive patterns

Strategy: validation

Validate before calling

if (!Array.isArray(transactions) || transactions.length === 0) return; // skip call
await fetchPost("/api/transaction", {transactions, reqId: Date.now()});

Type guard

const hasTransactions = (t) => Array.isArray(t) && t.length > 0;

Try / catch

try {
  await fetchPost("/api/transaction", payload);
} catch (e) {
  if (e.message.includes("must not be empty")) console.warn("skipped empty transaction batch");
  else throw e;
}

Prevention

When it happens

Trigger: POSTing /api/transaction with {"transactions": []}; clients building the payload but never pushing DoOperations into it; race where all operations were filtered out before sending.

Common situations: Optimistic-UI code that flushes an empty op queue; refactored clients omitting individual ops while still sending the envelope; test harnesses sending placeholder payloads.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/10ff1afcf066868f. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/transaction_decode.go:36

		return avDecodeParsedRequest[TransactionHistoryRequest](reader, "/api/transactions/undo")
	}
	PerformRedo.decodeRequest = func(reader io.Reader) (TransactionHistoryRequest, error) {
		return avDecodeParsedRequest[TransactionHistoryRequest](reader, "/api/transactions/redo")
	}
	PerformTransactions.decodeRequest = decodePerformTransactions
}

func decodePerformTransactions(reader io.Reader) (request PerformTransactionsRequest, err error) {
	fields, err := blockRequestFields(reader, "/api/transactions")
	if err != nil {
		return request, err
	}
	transactions, err := legacyField[[]json.RawMessage](fields, "transactions", "Array", true)
	if err != nil {
		return request, err
	}
	if len(transactions) == 0 {
		return request, fmt.Errorf("Field [transactions] must not be empty")
	}
	if request.ReqID, err = legacyField[float64](fields, "reqId", "Number", true); err != nil {
		return request, err
	}
	if request.App, err = legacyField[string](fields, "app", "String", false); err != nil {
		return request, err
	}
	if request.Session, err = legacyField[string](fields, "session", "String", false); err != nil {
		return request, err
	}
	request.TransactionJSON, request.DecodeError = normalizeTransactionJSON(fields["transactions"])
	if request.DecodeError == nil {
		request.DecodeError = json.Unmarshal(request.TransactionJSON, &request.Transactions)
	}
	return request, nil
}

// normalizeTransactionJSON 保留入口先按 JSON 数字读取、再绑定事务结构的数值语义。

View on GitHub (pinned to 9f775e8a12)