{"record":{"id":"1211bfb2604fb012","repo":"siyuan-note/siyuan","slug":"template-document-tree-plan-is-invalid-or-has-expi","errorCode":null,"errorMessage":"template document tree plan is invalid or has expired","messagePattern":"template document tree plan is invalid or has expired","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/template_doc_tree.go","lineNumber":486,"sourceCode":"\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 ||\n\t\trootTree.HPath != plan.rootHPath {\n\t\treturn false, errors.New(\"the document used to render the template has changed\")\n\t}\n\ttarget.templateDocTreeRootSnapshot = rootTree\n\tbox := Conf.Box(plan.boxID)\n\tif nil == box {\n\t\treturn false, ErrBoxNotFound\n\t}\n\tfor _, tree := range plan.trees {\n\t\tif nil == tree || nil == tree.Root || tree.ID != tree.Root.ID || tree.Box != plan.boxID || box.Exist(tree.Path) {\n\t\t\treturn false, errors.New(\"template document tree plan contains an invalid document snapshot\")\n\t\t}","sourceCodeStart":468,"sourceCodeEnd":504,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/template_doc_tree.go#L468-L504","documentation":"After the plan is found in the map, it is type-asserted to *templateDocTreePlan and checked for ID match and expiry (time.Now().After(plan.expiresAt)). This error means the stored entry is corrupt or of the wrong type, the ID does not match the entry, or the plan was still in the map when its TTL elapsed (e.g. the AfterFunc deletion raced with the attach).","triggerScenarios":"Transaction submitted within TTL but the AfterFunc timer fired concurrently and the map still holds the entry; internal map corruption/type mismatch; plan.expiresAt already passed at attach time; planID mutated on the client so it no longer equals plan.id.","commonSituations":"Attaching a plan right at the TTL boundary; clock skew if the plan was created on a machine with a different clock (rare, single-process); bugs in custom code that stores foreign values into templateDocTreePlans.","solutions":["Re-render the template to create a fresh plan and attach it promptly (well inside the TTL)","Do not mutate TemplateDocTreePlanID between plan creation and transaction submission","Check for races: ensure only one goroutine/transaction consumes a given plan ID","If this recurs, file a bug with kernel logs — a wrong-typed map value is an internal invariant violation"],"exampleFix":"// before\n// submitting 10 minutes after rendering; templateDocTreePlanTTL already elapsed\n// after\nplanID, _ := renderTemplateDocTree(...)\ngo func() { time.Sleep(jitter); tx.TemplateDocTreePlanID = planID; performTransactions([]*Transaction{tx}) }() // submit immediately after rendering","handlingStrategy":"retry","validationCode":"if time.Now().After(planExpiresAt) { re-render the plan before attaching }","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"plan is invalid or has expired\") { tx.TemplateDocTreePlanID = reRenderPlan(); retry }","preventionTips":["Submit the transaction immediately after rendering, never near the TTL boundary","Use one consumer goroutine per plan to avoid consume/expiry races","Never mutate the plan ID between creation and submission","Treat recurring wrong-type hits as a kernel bug and report with logs"],"tags":["go","transaction","template","expiry"],"backgroundTag":"resource-not-found","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"}