siyuan-note/siyuan · error
database entry [ ] has newer field values
Error message
database entry [%s] has newer field values
What it means
In the same replay check, prepareAttributeViewItemRemoval walks the live database's KeyValues and, for every row scheduled for removal (op.attributeViewItems.ItemIDs), requires that the current value deep-equals the value recorded in the snapshot. If the live row carries values newer than the snapshot (row was edited after the deletion snapshot captured the 'older' state), it fails with 'database entry [%s] has newer field values' rather than deleting fresher data.
Solutions
- Resolve the newer edits first (keep or merge them), then re-issue the removal without the stale snapshot
- Undo the row edits before undoing the deletion so the snapshot matches again
- Refresh the snapshot by performing the deletion anew on the current data
Example fix
// before
tx.removeAttrViewBlock(avID, itemIDs) // stale snapshot deletes newer edits
// after
for _, kv := range current.KeyValues {
for _, v := range kv.Values {
if slices.Contains(itemIDs, v.BlockID) && !reflect.DeepEqual(v, snapValue) {
// abort and re-capture snapshot from current data
return nil
}
}
} Defensive patterns
Strategy: validation
Validate before calling
for _, kv := range current.KeyValues {
for _, v := range kv.Values {
if slices.Contains(itemIDs, v.BlockID) && !reflect.DeepEqual(v, av.GetValue(snap.Keys, kv.Key.ID, v.BlockID)) {
return fmt.Errorf("row %s has newer values; re-capture snapshot", v.BlockID)
}
}
} Type guard
func rowsUnchangedSince(current *av.AttributeView, snap *model.AttributeViewItemsSnapshot, ids []string) bool {
for _, kv := range current.KeyValues {
for _, v := range kv.Values {
if slices.Contains(ids, v.BlockID) && !reflect.DeepEqual(v, av.GetValue(snap.Keys, kv.Key.ID, v.BlockID)) { return false }
}
}
return true
} Try / catch
if err := tx.prepareAttributeViewItemRemoval(op); err != nil {
if strings.Contains(err.Error(), "has newer field values") {
// keep newer data: re-issue deletion from current state instead of stale undo
return nil
}
return err
} Prevention
- Stop background writers (plugins, automation) before deleting rows
- Perform deletions from the current state rather than replaying old undos
- Reconcile sync conflicts on the rows before undoing deletions
When it happens
Trigger: Undo replay of doRemoveAttrViewBlock where a row present in ItemIDs was modified after the deletion snapshot was taken, so the current cell differs from op.attributeViewItems.Keys (checked only for rows in ItemIDs, complementing errorIndex 957 which checks snapshot values against current).
Common situations: User deleted a row via the database UI while a background process/plugin kept updating that row's cells; sync applied remote edits to the row after local deletion; undo stack replayed partially across sessions.
Related errors
- database back relation values
- database entry [ ] changed since deletion was undone
- database field [ ] changed after the operation
- database entry [ ] was removed after the field
- database [ ]
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/eaeccba31db7c8e6.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/attribute_view_items.go:62
current, err := tx.readAttributeViewItems(op.attributeViewItems)
if err != nil {
return err
}
if err = validateAttributeViewItemKeys(current, op.attributeViewItems); err != nil {
return err
}
for _, kv := range op.attributeViewItems.Keys {
for _, value := range kv.Values {
if !reflect.DeepEqual(value, av.GetValue(current.KeyValues, kv.Key.ID, value.BlockID)) {
return fmt.Errorf("database entry [%s] changed since deletion was undone", value.BlockID)
}
}
}
for _, kv := range current.KeyValues {
for _, value := range kv.Values {
if slices.Contains(op.attributeViewItems.ItemIDs, value.BlockID) &&
!reflect.DeepEqual(value, av.GetValue(op.attributeViewItems.Keys, kv.Key.ID, value.BlockID)) {
return fmt.Errorf("database entry [%s] has newer field values", value.BlockID)
}
}
}
if err = tx.prepareAttributeViewItemMutation(current, op.attributeViewItems); err != nil {
return err
}
return tx.replayAttributeViewItemConfiguration(op.attributeViewItems, false)
}
if tx.isReplay || !tx.fromAPI || len(tx.UndoOperations) == 0 {
return nil
}
current, err := avParseView(op.AvID, op.BlockID)
if err != nil {
return err
}
if current == nil {
return av.ErrViewNotFound
}View on GitHub (pinned to 9f775e8a12)