siyuan-note/siyuan · error
invalid database field replay
Error message
invalid database field replay
What it means
replayAttributeViewFields replays a recorded field-level change snapshot during transaction undo/redo replay. It first verifies the replay is active and that the operation's AvID and field ID match the snapshot recorded when the original mutation ran (op.AvID == state.avID && op.ID == state.keyID); otherwise it rejects the operation with 'invalid database field replay' because the recorded changes cannot safely be applied to a different database or field.
Solutions
- Build undo/redo transactions through the official kernel helpers so the attributeViewFields snapshot is attached and IDs are preserved
- Do not modify AvID/ID of generated operations between the original execution and its replay
- Clear the corrupted undo stack (or re-sync) so replay starts from a consistent state
Example fix
// before op.ID = otherFieldID // changed after snapshot capture // after // keep op.AvID/op.ID identical to the values captured in op.attributeViewFields
Defensive patterns
Strategy: try-catch
Validate before calling
if !tx.isReplay || op.attributeViewFields == nil || op.AvID != op.attributeViewFields.avID || op.ID != op.attributeViewFields.keyID {
return fmt.Errorf("skip replay: snapshot mismatch")
} Type guard
func snapshotMatches(op *model.Operation) bool {
s := op.attributeViewFields
return s != nil && op.AvID == s.avID && op.ID == s.keyID
} Try / catch
if err := tx.replayAttributeViewFields(op); err != nil {
if strings.Contains(err.Error(), "invalid database field replay") {
// drop the stale undo entry instead of failing the whole transaction
return nil
}
return err
} Prevention
- Do not mutate operation AvID/ID after creation
- Generate undo operations via kernel helpers that attach snapshots
- Avoid mixing hand-written and kernel-generated operations in one transaction
When it happens
Trigger: Undo/redo replay of doAddAttrViewColumn, doRemoveAttrViewColumn, doUpdateAttrViewColOptions, restoreDeletedAttributeViewBlocks, sortAttributeViewItem, or replayAttributeViewItemConfiguration where the operation's AvID or ID was mutated, or the snapshot (op.attributeViewFields) was not attached to the operation, or tx.isReplay is false when this path is entered.
Common situations: A plugin composed transaction operations manually and reordered or re-identified operations so the inverse no longer matches; an older client synced transactions produced by a newer kernel with different snapshot layout; snapshots dropped during serialization of a custom transaction.
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
- database back relation values
- database entry [ ] changed since deletion was undone
- database entry [ ] has newer field values
- database entry [ ] was removed after the field
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/558bbbba6cde7d33.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/attribute_view_fields.go:238
layout.Filters = av.RemoveFiltersByColumn(layout.Filters, keyID)
if len(layout.Filters) == 0 {
layout.Filters = []*av.ViewFilter{{Combination: av.FilterCombinationAnd}}
}
layout.Sorts = slices.DeleteFunc(layout.Sorts, func(sort *av.ViewSort) bool { return sort.Column == keyID })
if layout.Group != nil && layout.Group.Field == keyID {
removeAttributeViewGroup0(layout)
if layout.LayoutType == av.LayoutTypeKanban {
// 看板必须保留有效分组,将自动选择的替代分组一并纳入撤销快照。
setAttributeViewGroup(view, layout, &av.ViewGroup{Field: getKanbanPreferredGroupKey(view).ID})
}
}
}
}
func (tx *Transaction) replayAttributeViewFields(op *Operation) error {
state := op.attributeViewFields
if !tx.isReplay || op.AvID != state.avID || op.ID != state.keyID {
return errors.New("invalid database field replay")
}
if len(state.changes) == 0 {
return nil
}
before, after := map[string]*av.AttributeView{}, map[string]*av.AttributeView{}
for _, id := range sortedAttributeViewFieldKeys(state.changes) {
current, err := tx.readAttributeViewForMutation(id, state.blockID, state.boxID)
if err != nil {
return err
}
for keyID, typ := range state.fieldTypes[id] {
key, keyErr := current.GetKey(keyID)
if keyErr != nil || key.Type != typ {
return fmt.Errorf("database field [%s] changed after the operation", keyID)
}
}
// 恢复双向定义时,另一端的单向值必须仍与删除时一致。
for keyID, expected := range state.relationValues[id] {View on GitHub (pinned to 9f775e8a12)