{"record":{"id":"df4635a7846f0417","repo":"siyuan-note/siyuan","slug":"template-document-tree-plan-contains-an-invalid-pa","errorCode":null,"errorMessage":"template document tree plan contains an invalid parent undo operation","messagePattern":"template document tree plan contains an invalid parent undo operation","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/template_doc_tree.go","lineNumber":539,"sourceCode":"\t\t\ttemplateDocTreeRootID: plan.rootID,\n\t\t})\n\t}\n\treturn true, nil\n}\n\nfunc validateTemplateDocTreeParentOperations(transaction *Transaction) error {\n\tinverseActions := map[string]string{\n\t\t\"insert\":        \"delete\",\n\t\t\"delete\":        \"insert\",\n\t\t\"update\":        \"update\",\n\t\t\"foldHeading\":   \"unfoldHeading\",\n\t\t\"unfoldHeading\": \"foldHeading\",\n\t\t\"setAttrs\":      \"setAttrs\",\n\t}\n\tundoOperations := map[string]int{}\n\tfor _, operation := range transaction.UndoOperations {\n\t\tif nil == operation || \"\" == operation.ID {\n\t\t\treturn errors.New(\"template document tree plan contains an invalid parent undo operation\")\n\t\t}\n\t\tif _, supported := inverseActions[operation.Action]; !supported || \"\" != operation.RootID {\n\t\t\treturn errors.New(\"template document tree plan contains an unsupported parent undo operation\")\n\t\t}\n\t\tundoOperations[operation.Action+\"\\x00\"+operation.ID]++\n\t}\n\n\thasContentMutation := false\n\tfor _, operation := range transaction.DoOperations {\n\t\tif nil == operation || \"\" == operation.ID {\n\t\t\treturn errors.New(\"template document tree plan contains an invalid parent operation\")\n\t\t}\n\t\tinverseAction, supported := inverseActions[operation.Action]\n\t\tif !supported || \"\" != operation.RootID {\n\t\t\treturn errors.New(\"template document tree plan contains an unsupported parent operation\")\n\t\t}\n\t\tif \"insert\" == operation.Action || \"delete\" == operation.Action || \"update\" == operation.Action {\n\t\t\thasContentMutation = true","sourceCodeStart":521,"sourceCodeEnd":557,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/template_doc_tree.go#L521-L557","documentation":"validateTemplateDocTreeParentOperations pre-checks the transaction before a plan is attached. Every UndoOperation must be non-nil and carry a non-empty ID. This error means an undo operation is nil or missing its block ID, so it cannot be paired with a forward operation for reversibility.","triggerScenarios":"Constructing a Transaction for a template plan whose UndoOperations slice contains a nil entry or an Operation with ID \"\"; hand-assembled transactions in scripts/tests omitting IDs; upstream code that builds undo ops without setting IDs.","commonSituations":"Plugin or integration code manually building plan transactions instead of using the standard transaction builder; a serialization bug dropping empty IDs; tests constructing minimal fixtures.","solutions":["Populate Operation.ID for every entry in UndoOperations (the block ID being reverted)","Remove nil entries from the UndoOperations slice before calling performTransactions","Build the transaction via the kernel's normal operation pipeline so IDs are assigned consistently","Validate the transaction client-side (non-nil ops, non-empty IDs) before submission"],"exampleFix":"// before\nundoOps := []*Operation{{Action: \"delete\"}} // ID missing\n// after\nundoOps := []*Operation{{Action: \"delete\", ID: parentBlockID}}","handlingStrategy":"validation","validationCode":"for i, op := range tx.UndoOperations { if op == nil || op.ID == \"\" { return fmt.Errorf(\"undo operation %d missing ID\", i) } }","typeGuard":"func validUndoOps(ops []*Operation) bool { for _, op := range ops { if op == nil || op.ID == \"\" { return false } }; return len(ops) > 0 }","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"invalid parent undo operation\") { fix the transaction builder to assign IDs, then resubmit }","preventionTips":["Always set Operation.ID on undo operations","Filter nils from the UndoOperations slice before submission","Build transactions with the standard kernel pipeline, not by hand","Unit-test transaction fixtures for nil/empty IDs"],"tags":["go","transaction","validation","undo"],"backgroundTag":"empty-required-field","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}