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

  1. Reload the database and retry the undo once no other edits are in flight (close other editors / pause sync)
  2. Accept that undo cannot proceed across divergent edits and skip the conflicting undo step
  3. 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

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


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)