siyuan-note/siyuan · error
database entry [ ] was removed after the field
Error message
database entry [%s] was removed after the field
What it means
After applying the recorded patch, replayAttributeViewFields checks that any field values which are re-added during replay still have their owning rows: for every restored KeyValues entry whose field is not yet present in the live database, each value's BlockID must still exist as a block value (restored.GetBlockValue). If a row (database entry) that existed when the field was removed has since been deleted, replaying would resurrect orphaned field values, so the kernel refuses with this error.
Solutions
- Re-create (or restore via document history) the deleted rows before undoing the field removal
- Skip this undo step; re-add the field and re-enter the surviving values manually
- Adjust the snapshot/undo ordering so row deletions are undone before the field deletion
Example fix
// before
tx.undo(removeColumnOp) // rows already deleted -> error
// after
for _, rowID := range removedRowIDs {
if currentAv.GetBlockValue(rowID) == nil {
// restore row first (history/insertAttrViewBlock) before undoing the column
restoreRow(rowID)
}
} Defensive patterns
Strategy: validation
Validate before calling
for _, kv := range snapshotRestored.KeyValues {
if _, err := current.GetKey(kv.Key.ID); err == nil { continue }
for _, v := range kv.Values {
if current.GetBlockValue(v.BlockID) == nil {
return fmt.Errorf("row %s missing; restore rows before undoing field deletion", v.BlockID)
}
}
} Type guard
func rowsExist(av *av.AttributeView, ids []string) bool {
for _, id := range ids { if av.GetBlockValue(id) == nil { return false } }
return true
} Try / catch
if err := tx.replayAttributeViewFields(op); err != nil {
if strings.Contains(err.Error(), "was removed after the field") {
// restore rows from history first, then retry undo
return nil
}
return err
} Prevention
- Undo deletions in reverse order (rows before columns)
- Use document/database history to restore removed rows before schema undos
- Avoid pruning rows while older column deletions remain on the undo stack
When it happens
Trigger: Undoing a column deletion whose snapshot contains field values for rows that were themselves removed later (row deleted by user, plugin, or sync between the field deletion and the undo replay).
Common situations: User deleted rows right after removing a column and then hit undo repeatedly; sync merged a row deletion from another device; a plugin pruned detached rows while the undo stack still referenced them.
Related errors
- database back relation values
- database entry [ ] changed since deletion was undone
- database entry [ ] has newer field values
- database field [ ] changed after the operation
- database [ ]
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/3e001895c39607b6.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/attribute_view_fields.go:287
patched, _, err := state.changes[id].apply(data, true, op.attributeViewFieldUndo)
if err != nil {
return fmt.Errorf("database [%s]: %w", id, err)
}
encoded, err := json.Marshal(patched)
if err != nil {
return err
}
restored := &av.AttributeView{}
if err = json.Unmarshal(encoded, restored); err != nil {
return err
}
for _, kv := range restored.KeyValues {
if _, keyErr := current.GetKey(kv.Key.ID); keyErr == nil {
continue
}
for _, value := range kv.Values {
if restored.GetBlockValue(value.BlockID) == nil {
return fmt.Errorf("database entry [%s] was removed after the field", value.BlockID)
}
}
}
before[id], after[id] = current, restored
}
return tx.saveAttributeViewFieldChanges(state, before, after)
}
func (tx *Transaction) saveAttributeViewFieldChanges(state *attributeViewFieldsSnapshot, before, after map[string]*av.AttributeView) error {
if err := tx.rememberAttributeViewMutationTree(state.blockID); err != nil {
return err
}
ids := sortedAttributeViewFieldKeys(after)
for _, id := range ids {
key := state.boxID + "/" + id
if tx.attributeViewRollback.views[key] == nil {
tx.attributeViewRollback.views[key] = before[id]
}View on GitHub (pinned to 9f775e8a12)