siyuan-note/siyuan · error
database field [ ] changed after the operation
Error message
database field [%s] changed after the operation
What it means
During field-change replay, replayAttributeViewFields re-reads each affected database and compares every recorded field type (state.fieldTypes) against the live attribute view. If a recorded field is missing (GetKey fails) or its type differs, the database has moved on since the snapshot was captured and applying the change could destroy newer edits, so it fails with this error naming the changed field.
Solutions
- Reload the database and retry the undo once no other edits are in flight (close other editors / pause sync)
- Accept that undo cannot proceed across divergent edits and skip the conflicting undo step
- Re-create the field with its original type if you intentionally want the replayed state
Example fix
// before
// blind replay despite field type change
replay(tx, op)
// after
key, err := currentAv.GetKey(fieldID)
if err != nil || key.Type != expectedType {
// abort or refresh snapshot before replaying
return nil
} Defensive patterns
Strategy: validation
Validate before calling
av, err := model.ParseAttributeView(avID)
if err != nil { return err }
for fieldID, expectedType := range recordedFieldTypes {
key, err := av.GetKey(fieldID)
if err != nil || key.Type != expectedType {
return fmt.Errorf("field %s diverged; refresh snapshot before undo", fieldID)
}
} Type guard
func fieldTypesUnchanged(av *av.AttributeView, recorded map[string]av.KeyType) bool {
for id, t := range recorded {
if k, err := av.GetKey(id); err != nil || k.Type != t { return false }
}
return true
} Try / catch
if err := tx.replayAttributeViewFields(op); err != nil {
if strings.Contains(err.Error(), "changed after the operation") {
// skip or re-capture the undo step
return nil
}
return err
} Prevention
- Pause sync / close other editors while performing undo of schema changes
- Batch field-type changes and deletions deliberately, not interleaved with pending undos
- Verify field types after sync merges that touch the same database
When it happens
Trigger: Undo/redo replay of add/remove/update-column, restore-deleted-blocks, or sort-item operations when, between snapshot capture and replay, the field was deleted or its type changed (e.g. text converted to select) by a concurrent edit, another device's sync, or a manually composed transaction.
Common situations: User edited the database on another device while an undo was pending; sync merged divergent database definitions; a plugin changed a field type inside the same transaction that also deletes it, so the snapshot no longer matches.
Related errors
- database back relation values
- database entry [ ] changed since deletion was undone
- database entry [ ] has newer field values
- database entry [ ] was removed after the field
- database [ ]
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/b708ff2a838b9e25.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/attribute_view_fields.go:252
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] {
kv, keyErr := current.GetKeyValues(keyID)
if keyErr != nil {
return keyErr
}
if !reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected) {
return fmt.Errorf("database back relation values [%s] changed after field deletion", keyID)
}
}
data, err := attributeViewFieldJSON(current)
if err != nil {
return err
}
patched, _, err := state.changes[id].apply(data, true, op.attributeViewFieldUndo)
if err != nil {View on GitHub (pinned to 9f775e8a12)