{"record":{"id":"152dafca90255f38","repo":"siyuan-note/siyuan","slug":"template-document-tree-plans-cannot-be-attached-to","errorCode":null,"errorMessage":"template document tree plans cannot be attached to replay transactions","messagePattern":"template document tree plans cannot be attached to replay transactions","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/template_doc_tree.go","lineNumber":464,"sourceCode":"}\n\n// AttachTemplateDocTreePlans 将一次性计划转换为内核事务操作，父文档内容与全部子文档共用一条撤销记录。\nfunc AttachTemplateDocTreePlans(transactions []*Transaction) (attached bool, err error) {\n\tvar target *Transaction\n\tfor _, transaction := range transactions {\n\t\tif nil == transaction || \"\" == transaction.TemplateDocTreePlanID {\n\t\t\tcontinue\n\t\t}\n\t\tif nil != target || 1 != len(transactions) {\n\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\")","sourceCodeStart":446,"sourceCodeEnd":482,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/template_doc_tree.go#L446-L482","documentation":"AttachTemplateDocTreePlans rejects plans attached to transactions marked isReplay. Replay transactions are internal re-executions (e.g. after undo/redo or sync replay); attaching a document-tree plan there would re-run document creation outside the intended user-initiated flow. The plan must ride on a fresh, non-replay transaction.","triggerScenarios":"A transaction carrying TemplateDocTreePlanID being pushed through the replay path of performTransactions — for example when redo re-executes a stored transaction, or sync/replay machinery re-processes a transaction that still has a plan ID attached.","commonSituations":"Custom tooling that stores and re-submits captured transactions including their plan IDs; redoing a template transaction where the plan ID was not cleared before replay; sync engines replaying remote transaction logs that contain plan markers.","solutions":["Clear TemplateDocTreePlanID from a transaction before re-submitting or replaying it (AttachTemplateDocTreePlans already does this on the target before persisting)","Re-issue the template document tree creation as a fresh transaction instead of replaying the old one","If replaying captured transactions, strip plan metadata first and re-attach via the normal render flow","Exclude template-plan transactions from any custom sync/replay pipeline"],"exampleFix":"// before: replaying a stored transaction as-is\nreplayTx := storedTx\nperformTransactions([]*Transaction{replayTx})\n// after: strip plan metadata before replay\nstoredTx.TemplateDocTreePlanID = \"\"\nstoredTx.isReplay = false\nperformTransactions([]*Transaction{storedTx})","handlingStrategy":"validation","validationCode":"// Go: refuse to replay transactions that still carry plan metadata\nif tx.isReplay && tx.TemplateDocTreePlanID != \"\" {\n    return errors.New(\"strip TemplateDocTreePlanID before replay\")\n}","typeGuard":null,"tryCatchPattern":"if _, err := AttachTemplateDocTreePlans([]*Transaction{tx}); err != nil && strings.Contains(err.Error(), \"replay\") {\n    tx.TemplateDocTreePlanID = \"\"\n    return performTransactions([]*Transaction{tx})\n}","preventionTips":["Clear TemplateDocTreePlanID before storing or re-submitting transactions","Exclude template-plan transactions from undo/redo replay capture","Re-create document trees via the normal template flow instead of replaying old transactions","Keep plan metadata out of any synced or logged transaction payloads"],"tags":["transaction","replay","template-doc-tree","siyuan-kernel"],"backgroundTag":"invalid-state-transition","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"}