siyuan-note/siyuan · warning

database field undo conflicts with a subsequent edit

Error message

database field undo conflicts with a subsequent edit

What it means

apply is the undo engine for recorded attribute-view field changes. When undoing a change, it expects the current JSON structure to match the shape recorded at edit time (change.members != nil requires current to be an existing map). If the current value is missing (exists==false) or has a different type, the recorded change cannot be reversed, so the undo is rejected with this conflict error. It protects against silently corrupting data when later edits diverged from the undo log.

Solutions

  1. Stop undoing past the conflict point — the undo stack is no longer linearly reversible; refresh the document instead
  2. Redo/redo-history: restore the desired state via history (data history feature) rather than undo
  3. Avoid manual edits to attribute view storage so recorded shapes stay consistent with live data
  4. If it happens reproducibly, capture the transaction and report the conflict
Defensive patterns

Strategy: try-catch

Validate before calling

// before undoing, verify the recorded target still matches the live structure
if _, ok := currentJSON[recordedKey]; !ok { return errors.New("undo target changed; refresh required") }

Type guard

func isUndoConflict(err error) bool { return strings.Contains(err.Error(), "conflicts with a subsequent edit") }

Try / catch

if err := undo(); err != nil {
    if isUndoConflict(err) {
        clearUndoStack(); refreshDocument() // stack no longer reversible
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Undoing a database field edit after subsequent edits restructured the underlying map: the target key/value was deleted, the container itself was removed, or the stored value is not a map[string]any anymore.

Common situations: Undoing many steps where an intermediate operation deleted the database field; redo/undo after a sync merged conflicting changes; corrupted or manually edited JSON in the attribute view storage.

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


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/ada3ebe489d62525. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view_field_changes.go:207

	}
	change.elements = map[string]*attributeViewFieldChange{}
	change.beforeOrder, change.afterOrder = oldOrder, newOrder
	for _, id := range append(append([]string(nil), oldOrder...), newOrder...) {
		old, oldExists := oldElements[id]
		newValue, newExists := newElements[id]
		if child := diffAttributeViewFieldData(old, newValue, oldExists, newExists, container); child != nil {
			change.elements[id] = child
		}
	}
	if len(change.elements) == 0 {
		return nil
	}
	change.before, change.after = nil, nil
	return change
}

func (change *attributeViewFieldChange) apply(current any, exists, undo bool) (any, bool, error) {
	conflict := fmt.Errorf("database field undo conflicts with a subsequent edit")
	if change.members != nil {
		object, ok := current.(map[string]any)
		if !exists || !ok {
			return nil, false, conflict
		}
		for _, key := range sortedAttributeViewFieldKeys(change.members) {
			value, present := object[key]
			replacement, keep, err := change.members[key].apply(value, present, undo)
			if err != nil {
				return nil, false, fmt.Errorf("%s: %w", key, err)
			}
			if keep {
				object[key] = replacement
			} else {
				delete(object, key)
			}
		}
		return object, true, nil

View on GitHub (pinned to 9f775e8a12)