{"record":{"id":"c5d8e7c6c016b814","repo":"siyuan-note/siyuan","slug":"template-document-tree-plan-is-missing-or-has-expi","errorCode":null,"errorMessage":"template document tree plan is missing or has expired","messagePattern":"template document tree plan is missing or has expired","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/template_doc_tree.go","lineNumber":482,"sourceCode":"\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 ||\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}","sourceCodeStart":464,"sourceCodeEnd":500,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/template_doc_tree.go#L464-L500","documentation":"AttachTemplateDocTreePlans looks up the pending template document tree plan by the ID stored on the transaction (TemplateDocTreePlanID) using sync.Map.LoadAndDelete. This error means no plan is registered under that ID: the plan was never created, was already consumed by a previous attach, or the TTL timer (time.AfterFunc) evicted it. Plans are one-shot and in-memory only, so any replay or retry of the same transaction with a stale plan ID fails here.","triggerScenarios":"Calling performTransactions with a transaction whose TemplateDocTreePlanID references (a) an ID never registered via the plan collector, (b) a plan already consumed by an earlier AttachTemplateDocTreePlans call (LoadAndDelete removes it), (c) a plan older than templateDocTreePlanTTL, or (d) a kernel restart which wiped the in-memory map.","commonSituations":"Retrying a failed HTTP transaction request with the same plan ID; a slow client that waits longer than the plan TTL before committing; undo/redo replay reusing a recorded plan ID; multiple kernel instances behind a proxy where the plan was created in another process.","solutions":["Re-render the template to obtain a fresh plan ID and send a new transaction instead of retrying with the old TemplateDocTreePlanID","Reduce the delay between plan creation and transaction submission so it stays within templateDocTreePlanTTL","Ensure each plan ID is used exactly once; do not reuse it for retries or duplicates","Verify the client talks to the same running kernel instance that created the plan (no restart or multi-process split)"],"exampleFix":"// before\ntransaction.TemplateDocTreePlanID = stalePlanID // reused after a failed attempt\nperformTransactions([]*Transaction{transaction})\n// after\nplanID, summary := renderTemplateDocTree(...) // re-render to get a fresh one-shot plan\ntransaction.TemplateDocTreePlanID = planID\nperformTransactions([]*Transaction{transaction})","handlingStrategy":"retry","validationCode":"if planID == \"\" || time.Since(planCreatedAt) > planTTL || planConsumed[planID] { re-render plan before submitting }","typeGuard":null,"tryCatchPattern":"err := attachAndPerform(tx); if err != nil && strings.Contains(err.Error(), \"plan is missing or has expired\") { planID = reRenderPlan(); tx.TemplateDocTreePlanID = planID; retry once }","preventionTips":["Apply each plan exactly once immediately after rendering","Never reuse a plan ID across retries; re-render instead","Keep render-to-apply latency well under the plan TTL","Assume plans are lost on kernel restart and re-render after any restart"],"tags":["go","transaction","template","expired-state"],"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-14T00:17:10.932Z"}