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
- Split the transaction: put attribute-view operations in a separate av transaction and block operations in a block-only transaction
- Clear Srcs/CellUpdates for pure block operations — pass the DOM in Data and leave av fields nil
- Check the operation's Action and route it through the matching contract converter (block vs av) instead of assuming block
- 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
- Keep block and attribute-view operations in separate transactions
- Never populate Srcs/CellUpdates on block actions like 'insert'/'update'
- Assert operation kind (isBlockOperation) before contract conversion in shared helpers
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
- block operation context
- attribute view spec is too new
- database [ ] template field [ ] rendering failed
- filter nesting depth exceeds the maximum allowed
- item not found
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)