siyuan-note/siyuan · error

invalid database binding replay

Error message

invalid database binding replay

What it means

Raised during undo/redo replay of a database binding replacement. The stored binding snapshot must match the replayed operation (same attribute view ID and same previous block ID); if it does not, the transaction refuses to apply a replay that would desynchronize the attribute view from the recorded undo state.

Solutions

  1. Do not construct replaceAttrViewBlock operations manually; use the UI or API so the binding snapshot is recorded
  2. Re-open the document so the undo stack is rebuilt consistently, then redo the binding change
  3. Check whether an extension/plugin is issuing raw transactions and fix it to fill op.attributeViewBinding
  4. Report/reproduce with the transaction log if the built-in undo produces this; it indicates an undo-stack bug

Example fix

// before (raw op, no snapshot)
tx.appendOp(&Operation{Action: 'replaceAttrViewBlock', AvID: avID, PreviousID: oldID, NextID: newID})
// after (record binding state so undo/redo can replay)
op.attributeViewBinding = captureAttributeViewBinding(avID, oldID)
tx.appendOp(op)
Defensive patterns

Strategy: try-catch

Validate before calling

// before replaying, confirm the recorded snapshot matches the op
if (!op.attributeViewBinding || op.attributeViewBinding.avID !== op.avID ||
    op.attributeViewBinding.before.blockID !== op.previousID) {
  skipReplay(op);
}

Try / catch

try {
  await tx.doReplaceAttrViewBlock(op);
} catch (e) {
  if (e.message.indexOf('invalid database binding replay') !== -1) {
    rebuildUndoStack();
  }
}

Prevention

When it happens

Trigger: doReplaceAttrViewBlock invoked with isReplay=true (undo/redo) while op.attributeViewBinding is nil, its avID differs from op.AvID, or snapshot before.BlockID differs from op.PreviousID — i.e. the undo stack contains an operation recorded against a different database or block than the one being replayed.

Common situations: Undoing a multi-step session where database binding ops were interleaved with other transactions; attribute view deleted/re-created between the original operation and the undo; custom or scripted transaction construction that omits attributeViewBinding snapshots.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at kernel/model/attribute_view_binding_undo.go:41

	"github.com/88250/lute/ast"
	"github.com/88250/lute/parse"
	"github.com/siyuan-note/siyuan/kernel/av"
	"github.com/siyuan-note/siyuan/kernel/util"
)

// 换绑快照只保留主键值,条目 ID 和其他字段值始终保持不变。
type attributeViewBindingSnapshot struct {
	avID, blockID, boxID string
	before, after        *av.Value
	duplicateItemID      string
}

func (tx *Transaction) replaceAttributeViewBinding(op *Operation) error {
	state := op.attributeViewBinding
	if state != nil {
		if !tx.isReplay || state.avID != op.AvID || state.before.BlockID != op.PreviousID {
			return fmt.Errorf("invalid database binding replay")
		}
		current, err := tx.readAttributeViewForMutation(state.avID, state.blockID, state.boxID)
		if err != nil {
			return err
		}
		if state.duplicateItemID != "" {
			op.RetData = map[string]any{"targetItemID": state.duplicateItemID, "duplicate": true}
			return nil
		}
		expected, desired := state.before, state.after
		if op.attributeViewBindingUndo {
			expected, desired = desired, expected
		}
		value := current.GetBlockValue(expected.BlockID)
		if value == nil || value.Block == nil || value.ID != expected.ID || value.KeyID != expected.KeyID ||
			value.IsDetached != expected.IsDetached || value.Block.ID != expected.Block.ID {
			return fmt.Errorf("database entry binding [%s] changed", expected.BlockID)
		}

View on GitHub (pinned to 9f775e8a12)