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
- Do not construct replaceAttrViewBlock operations manually; use the UI or API so the binding snapshot is recorded
- Re-open the document so the undo stack is rebuilt consistently, then redo the binding change
- Check whether an extension/plugin is issuing raw transactions and fix it to fill op.attributeViewBinding
- 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
- Never issue raw replaceAttrViewBlock transactions without capturing attributeViewBinding state
- Avoid mixing plugin-issued transactions with UI undo history
- Keep undo stacks within one session; reload after sync to resync state
- Test undo/redo after database binding changes in your plugin workflows
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
- invalid database entry replay
- invalid database field replay
- database back relation values
- database entry [ ] changed since deletion was undone
- database entry [ ] has newer field values
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)