{"record":{"id":"558bbbba6cde7d33","repo":"siyuan-note/siyuan","slug":"invalid-database-field-replay","errorCode":null,"errorMessage":"invalid database field replay","messagePattern":"invalid database field replay","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view_fields.go","lineNumber":238,"sourceCode":"\t\tlayout.Filters = av.RemoveFiltersByColumn(layout.Filters, keyID)\n\t\tif len(layout.Filters) == 0 {\n\t\t\tlayout.Filters = []*av.ViewFilter{{Combination: av.FilterCombinationAnd}}\n\t\t}\n\t\tlayout.Sorts = slices.DeleteFunc(layout.Sorts, func(sort *av.ViewSort) bool { return sort.Column == keyID })\n\t\tif layout.Group != nil && layout.Group.Field == keyID {\n\t\t\tremoveAttributeViewGroup0(layout)\n\t\t\tif layout.LayoutType == av.LayoutTypeKanban {\n\t\t\t\t// 看板必须保留有效分组，将自动选择的替代分组一并纳入撤销快照。\n\t\t\t\tsetAttributeViewGroup(view, layout, &av.ViewGroup{Field: getKanbanPreferredGroupKey(view).ID})\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (tx *Transaction) replayAttributeViewFields(op *Operation) error {\n\tstate := op.attributeViewFields\n\tif !tx.isReplay || op.AvID != state.avID || op.ID != state.keyID {\n\t\treturn errors.New(\"invalid database field replay\")\n\t}\n\tif len(state.changes) == 0 {\n\t\treturn nil\n\t}\n\tbefore, after := map[string]*av.AttributeView{}, map[string]*av.AttributeView{}\n\tfor _, id := range sortedAttributeViewFieldKeys(state.changes) {\n\t\tcurrent, err := tx.readAttributeViewForMutation(id, state.blockID, state.boxID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor keyID, typ := range state.fieldTypes[id] {\n\t\t\tkey, keyErr := current.GetKey(keyID)\n\t\t\tif keyErr != nil || key.Type != typ {\n\t\t\t\treturn fmt.Errorf(\"database field [%s] changed after the operation\", keyID)\n\t\t\t}\n\t\t}\n\t\t// 恢复双向定义时，另一端的单向值必须仍与删除时一致。\n\t\tfor keyID, expected := range state.relationValues[id] {","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_fields.go#L220-L256","documentation":"replayAttributeViewFields replays a recorded field-level change snapshot during transaction undo/redo replay. It first verifies the replay is active and that the operation's AvID and field ID match the snapshot recorded when the original mutation ran (op.AvID == state.avID && op.ID == state.keyID); otherwise it rejects the operation with 'invalid database field replay' because the recorded changes cannot safely be applied to a different database or field.","triggerScenarios":"Undo/redo replay of doAddAttrViewColumn, doRemoveAttrViewColumn, doUpdateAttrViewColOptions, restoreDeletedAttributeViewBlocks, sortAttributeViewItem, or replayAttributeViewItemConfiguration where the operation's AvID or ID was mutated, or the snapshot (op.attributeViewFields) was not attached to the operation, or tx.isReplay is false when this path is entered.","commonSituations":"A plugin composed transaction operations manually and reordered or re-identified operations so the inverse no longer matches; an older client synced transactions produced by a newer kernel with different snapshot layout; snapshots dropped during serialization of a custom transaction.","solutions":["Build undo/redo transactions through the official kernel helpers so the attributeViewFields snapshot is attached and IDs are preserved","Do not modify AvID/ID of generated operations between the original execution and its replay","Clear the corrupted undo stack (or re-sync) so replay starts from a consistent state"],"exampleFix":"// before\nop.ID = otherFieldID // changed after snapshot capture\n// after\n// keep op.AvID/op.ID identical to the values captured in op.attributeViewFields\n","handlingStrategy":"try-catch","validationCode":"if !tx.isReplay || op.attributeViewFields == nil || op.AvID != op.attributeViewFields.avID || op.ID != op.attributeViewFields.keyID {\n    return fmt.Errorf(\"skip replay: snapshot mismatch\")\n}","typeGuard":"func snapshotMatches(op *model.Operation) bool {\n    s := op.attributeViewFields\n    return s != nil && op.AvID == s.avID && op.ID == s.keyID\n}","tryCatchPattern":"if err := tx.replayAttributeViewFields(op); err != nil {\n    if strings.Contains(err.Error(), \"invalid database field replay\") {\n        // drop the stale undo entry instead of failing the whole transaction\n        return nil\n    }\n    return err\n}","preventionTips":["Do not mutate operation AvID/ID after creation","Generate undo operations via kernel helpers that attach snapshots","Avoid mixing hand-written and kernel-generated operations in one transaction"],"tags":["database","attribute-view","undo-redo","replay"],"backgroundTag":"invalid-state-transition","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}