{"record":{"id":"010e786b555fcb84","repo":"siyuan-note/siyuan","slug":"template-document-tree-transaction-contains-a-rese","errorCode":null,"errorMessage":"template document tree transaction contains a reserved operation","messagePattern":"template document tree transaction contains a reserved operation","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/template_doc_tree.go","lineNumber":474,"sourceCode":"\t\t\treturn false, errors.New(\"a document tree plan must be applied in a single transaction\")\n\t\t}\n\t\ttarget = transaction\n\t}\n\tif nil == target {\n\t\treturn false, nil\n\t}\n\tif target.isReplay {\n\t\treturn false, errors.New(\"template document tree plans cannot be attached to replay transactions\")\n\t}\n\tif 0 == len(target.DoOperations) || 0 == len(target.UndoOperations) {\n\t\treturn false, errors.New(\"template document tree plan requires reversible parent operations\")\n\t}\n\tif err = validateTemplateDocTreeParentOperations(target); nil != err {\n\t\treturn false, err\n\t}\n\tfor _, operation := range append(append([]*Operation{}, target.DoOperations...), target.UndoOperations...) {\n\t\tif nil != operation && (\"restoreCreatedDoc\" == operation.Action || \"removeCreatedDoc\" == operation.Action) {\n\t\t\treturn false, errors.New(\"template document tree transaction contains a reserved operation\")\n\t\t}\n\t}\n\n\tplanID := target.TemplateDocTreePlanID\n\ttarget.TemplateDocTreePlanID = \"\"\n\tvalue, loaded := templateDocTreePlans.LoadAndDelete(planID)\n\tif !loaded {\n\t\treturn false, errors.New(\"template document tree plan is missing or has expired\")\n\t}\n\tplan, ok := value.(*templateDocTreePlan)\n\tif !ok || plan.id != planID || time.Now().After(plan.expiresAt) {\n\t\treturn false, errors.New(\"template document tree plan is invalid or has expired\")\n\t}\n\tif !transactionTargetsTemplateRoot(target, plan.rootID, plan.boxID) {\n\t\treturn false, errors.New(\"template document tree plan does not match the edited document\")\n\t}\n\trootTree, loadErr := LoadTreeByBlockID(plan.rootID)\n\tif nil != loadErr || nil == rootTree || rootTree.Box != plan.boxID || rootTree.Path != plan.rootPath ||","sourceCodeStart":456,"sourceCodeEnd":492,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/template_doc_tree.go#L456-L492","documentation":"After structural validation, AttachTemplateDocTreePlans scans every do and undo operation for the reserved actions 'restoreCreatedDoc' and 'removeCreatedDoc'. These actions are owned exclusively by the document-tree plan machinery (used for compensation/cleanup of created documents); a user-supplied transaction containing them is rejected to prevent conflicts with the plan's own generated operations.","triggerScenarios":"Crafting a transaction whose TemplateDocTreePlanID is set and whose DoOperations/UndoOperations include an operation with Action 'restoreCreatedDoc' or 'removeCreatedDoc'; performTransactions routes it through AttachTemplateDocTreePlans and rejects it.","commonSituations":"Scripts or tests reusing internal compensation actions in custom transactions; copying operation objects from a previously executed plan transaction into a new one; attempting to manually trigger the cleanup actions that the plan system generates internally.","solutions":["Remove operations with the reserved actions 'restoreCreatedDoc'/'removeCreatedDoc' from the manually built transaction","Let the plan engine generate its own compensation operations — do not pre-seed them","If you need cleanup semantics, rely on undo/redo of the plan transaction rather than emitting the reserved actions yourself","Use distinct custom action names for your own operation types"],"exampleFix":"// before: reusing a reserved action\nops = append(ops, &Operation{Action: \"removeCreatedDoc\", ID: docID})\n// after: use a non-reserved action\ntx := &Transaction{TemplateDocTreePlanID: planID, DoOperations: doOps, UndoOperations: undoOps}\nplanEngine.Apply(tx) // engine emits its own compensation ops","handlingStrategy":"validation","validationCode":"// Go: reject reserved actions before submission\nreserved := map[string]bool{\"restoreCreatedDoc\": true, \"removeCreatedDoc\": true}\nfor _, op := range append(tx.DoOperations, tx.UndoOperations...) {\n    if op != nil && reserved[op.Action] {\n        return fmt.Errorf(\"action %q is reserved for the plan engine\", op.Action)\n    }\n}","typeGuard":null,"tryCatchPattern":"if _, err := AttachTemplateDocTreePlans([]*Transaction{tx}); err != nil && strings.Contains(err.Error(), \"reserved operation\") {\n    return sanitizeReservedActions(tx)\n}","preventionTips":["Treat 'restoreCreatedDoc' and 'removeCreatedDoc' as internal-only actions","Namespace custom operation actions distinctly from plan-engine actions","Do not copy operations from executed plan transactions into new ones","Request cleanup by undoing the plan transaction, not by emitting reserved actions"],"tags":["transaction","reserved-operation","template-doc-tree","siyuan-kernel"],"backgroundTag":"unsupported-operation","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"}