siyuan-note/siyuan · error
database back relation field
Error message
database back relation field [%s] changed
What it means
During replay of an insert operation containing two-way relation values, the kernel checks the target database's back-relation field: it must still be a relation key, still two-way, and still point back to this database and key (matching AvID and BackKeyID). If the relation pair was edited, unlinked, or reconfigured since the snapshot, replaying would write into a mismatched field, so it aborts.
Solutions
- Restore the relation field's two-way configuration (same AvID/BackKeyID) before replaying the operation
- Undo the field-configuration change that broke the relation pair, then retry
- Re-create the two-way relation between the two databases and re-add the entries manually instead of replaying
- Check both databases' key definitions (Type, Relation.IsTwoWay, AvID, BackKeyID) for consistency before scripted edits
Defensive patterns
Strategy: validation
Validate before calling
// verify the back-relation field config before writing relations
const target = await getAttrView(relAvID);
const backKey = target.keyTypes[relBackKeyID];
if (!backKey || backKey.type !== 'relation' || !backKey.relation?.isTwoWay || backKey.relation.avID !== srcAvID || backKey.relation.backKeyID !== srcKeyID) throw new Error('back relation field changed'); Type guard
function isTwoWayBackRelation(k, srcAvID, srcKeyID) { return !!k && k.type === 'relation' && !!k.relation && k.relation.isTwoWay === true && k.relation.avID === srcAvID && k.relation.backKeyID === srcKeyID; } Try / catch
try { await insertAttrViewBlock(avID, blockID, items); } catch (e) { if (/back relation field \[.+\] changed/.test(e.message)) { restoreRelationConfig(); retry(); } else throw e; } Prevention
- Freeze relation field configuration while scripts manipulate related entries
- After editing a relation column, verify its pair field still points back correctly
- Avoid merging schema changes from sync while undo history is pending
When it happens
Trigger: Replaying doInsertAttrViewBlock with two-way relation values after the back-relation field in the target database was deleted, converted to another type, changed to one-way, or re-pointed to a different key/AV.
Common situations: User edited relation column configuration between the original operation and undo/redo; a plugin or script rewired two-way relation metadata; sync merged divergent relation field definitions.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- related database entry
- database back relation changed since document deletion
- database back relation field
- database back relation value is invalid
- database back relation values
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/8926d1d9f32f7a77.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/attribute_view_items.go:351
for _, value := range kv.Values {
if value.Relation == nil || len(value.Relation.BlockIDs) == 0 {
continue
}
dest := changed[kv.Key.Relation.AvID]
if dest == nil {
dest, err = av.ParseAttributeViewForIndexInBox(kv.Key.Relation.AvID, state.BoxID)
if err != nil {
return err
}
if dest == nil {
return av.ErrViewNotFound
}
changed[dest.ID] = dest
}
back, backErr := dest.GetKeyValues(kv.Key.Relation.BackKeyID)
if backErr != nil || back.Key.Type != av.KeyTypeRelation || back.Key.Relation == nil ||
!back.Key.Relation.IsTwoWay || back.Key.Relation.AvID != state.AvID || back.Key.Relation.BackKeyID != kv.Key.ID {
return fmt.Errorf("database back relation field [%s] changed", kv.Key.Relation.BackKeyID)
}
for _, id := range value.Relation.BlockIDs {
backValue := back.GetValue(id)
if dest.GetBlockValue(id) == nil || backValue == nil || backValue.Relation == nil {
return fmt.Errorf("related database entry [%s] changed", id)
}
if !slices.Contains(backValue.Relation.BlockIDs, value.BlockID) {
backValue.Relation.BlockIDs = append(backValue.Relation.BlockIDs, value.BlockID)
}
}
}
}
for _, view := range current.Views {
view.ItemIDs = restoreAttributeViewItemOrder(view.ItemIDs, state.Orders[view.ID], state.ItemIDs)
for _, group := range view.Groups {
group.GroupItemIDs = restoreAttributeViewItemOrder(group.GroupItemIDs, state.Orders[group.ID], state.ItemIDs)
}
}View on GitHub (pinned to 9f775e8a12)