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
- Parse the key prefix before the first ": " to locate the offending field
- Use errors.Is(err, conflictErr) semantics — the wrapped cause remains a database field undo conflict
- 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
- Unwrap with errors.Is/As to get the underlying conflict instead of string matching only
- Keep member edits atomic so partial structure changes don't break undo
- Test undo paths that span multiple member mutations
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
- block swap undo state is unavailable
- cannot replay block swap across encrypted notebook…
- database binding [ ] already exists
- database field undo conflicts with a subsequent edit
- prepare export asset
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)