siyuan-note/siyuan · warning

%s: %w

Error message

%s: %w

What it means

When a nested member change inside an attributeViewFieldChange fails to apply, apply wraps the child error with the member key name using fmt.Errorf("%s: %w", key, err), producing messages like "foo: database field undo conflicts with a subsequent edit". The wrapping preserves the original error for errors.Is checks while pinpointing which field of the map failed. It is a path-annotating wrapper, not a new failure mode.

Solutions

  1. Parse the key prefix before the first ": " to locate the offending field
  2. Use errors.Is(err, conflictErr) semantics — the wrapped cause remains a database field undo conflict
  3. Re-sync or reload the attribute view so its structure matches before further undo attempts

Example fix

// before
if err != nil { log.Println(err) }
// after
if err != nil {
    if strings.Contains(err.Error(), "conflicts with a subsequent edit") { reloadAttributeView() }
    log.Println(err)
}
Defensive patterns

Strategy: try-catch

Type guard

func isUndoConflict(err error) bool { return errors.Is(err, errDatabaseFieldUndoConflict) }

Try / catch

if err := undo(); err != nil {
    if key, cause, ok := splitKeyedError(err); ok && isUndoConflict(cause) {
        log.Printf("member %q cannot be undone: %v", key, cause)
    }
}

Prevention

When it happens

Trigger: Undoing a composite field change where a specific member (key) fails its own apply — e.g. that member was deleted or retyped by a later edit.

Common situations: Multi-field database updates undone after one field was separately removed; parsing which field caused a bulk undo failure; writing tests that assert on nested error text.

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/a68e52dfb3a49719. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view_field_changes.go:217

	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
	}
	if change.elements != nil {
		values, ok := current.([]any)
		if !exists || !ok {
			return nil, false, conflict
		}
		elements, _, valid := attributeViewFieldElements(values, change.container)
		if !valid {
			return nil, false, conflict
		}

View on GitHub (pinned to 9f775e8a12)