siyuan-note/siyuan · error

unexpected attribute view payload in block operation

Error message

unexpected attribute view payload in block operation [%s]

What it means

blockOperationContract converts kernel-side model.Operation values into the public apicontract.BlockOperation shape for block-only transactions. Block operations must carry DOM payload in Data and no attribute-view payload; if Srcs (attribute-view source value IDs) is non-nil or CellUpdates is non-empty, the operation is an av operation misclassified as a block operation, and conversion refuses with this error naming the offending Action.

Solutions

  1. Split the transaction: put attribute-view operations in a separate av transaction and block operations in a block-only transaction
  2. Clear Srcs/CellUpdates for pure block operations — pass the DOM in Data and leave av fields nil
  3. Check the operation's Action and route it through the matching contract converter (block vs av) instead of assuming block
  4. If triggered by stored/replayed transactions, re-record or migrate them after upgrading the kernel

Example fix

// before
ops := []*model.Operation{
  {Action: "insert", ID: id, Data: dom, Srcs: avSrcs},
}
// after
blockOps := []*model.Operation{{Action: "insert", ID: id, Data: dom}}
avOps := []*model.Operation{{Action: "insertAttrViewBlock", Srcs: avSrcs}}
// issue blockOps and avOps as separate transactions
Defensive patterns

Strategy: type-guard

Validate before calling

const hasAvPayload = (op) => op && (Array.isArray(op.Srcs) && op.Srcs.length > 0 || Array.isArray(op.CellUpdates) && op.CellUpdates.length > 0);
if (hasAvPayload(op)) throw new Error(`operation ${op.Action} is an attribute-view op; route it through the av contract`);

Type guard

const isBlockOperation = (op) => !!op && (op.Srcs == null || op.Srcs.length === 0) && (!op.CellUpdates || op.CellUpdates.length === 0);

Try / catch

try {
  return convertOperationContract(op);
} catch (e) {
  if (String(e?.message ?? e?.msg).includes('unexpected attribute view payload')) {
    return convertAvOperation(op); // route av ops to their own converter
  }
  throw e;
}

Prevention

When it happens

Trigger: Building or replaying a transaction where an attribute-view operation (e.g. insertAttrViewBlock / setAttrViewCell with Srcs or CellUpdates) is placed in the DoOperations list that feeds blockOperationContract — e.g. a plugin mixing av operations into a block transaction, or a kernel bug mislabeling the operation kind.

Common situations: Plugin code pushing transactions with mixed operation types; contract conversion running over legacy transactions that combined block and av operations; a kernel upgrade changing operation payload shape so old stored transactions hit this path.

Related errors


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

Appendix: source

Thrown at kernel/api/contract_block_transaction.go:16

package api

import (
	"encoding/json"
	"fmt"

	"github.com/siyuan-note/siyuan/kernel/apicontract"
	"github.com/siyuan-note/siyuan/kernel/model"
)

func blockOperationContract(operation *model.Operation) (*apicontract.BlockOperation, error) {
	if operation == nil {
		return nil, nil
	}
	if operation.Srcs != nil || len(operation.CellUpdates) > 0 {
		return nil, fmt.Errorf("unexpected attribute view payload in block operation [%s]", operation.Action)
	}
	ret := &apicontract.BlockOperation{
		Action:            operation.Action,
		ID:                operation.ID,
		RootID:            operation.RootID,
		ParentID:          operation.ParentID,
		PreviousID:        operation.PreviousID,
		NextID:            operation.NextID,
		BlockIDs:          operation.BlockIDs,
		BlockID:           operation.BlockID,
		DeckID:            operation.DeckID,
		AvID:              operation.AvID,
		SrcIDs:            operation.SrcIDs,
		IsDetached:        operation.IsDetached,
		Name:              operation.Name,
		Typ:               operation.Typ,
		Format:            operation.Format,
		KeyID:             operation.KeyID,

View on GitHub (pinned to 9f775e8a12)