siyuan-note/siyuan · error

template document tree plan is invalid or has expired

Error message

template document tree plan is invalid or has expired

What it means

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).

Source

Thrown at kernel/model/template_doc_tree.go:486

	}
	if err = validateTemplateDocTreeParentOperations(target); nil != err {
		return false, err
	}
	for _, operation := range append(append([]*Operation{}, target.DoOperations...), target.UndoOperations...) {
		if nil != operation && ("restoreCreatedDoc" == operation.Action || "removeCreatedDoc" == operation.Action) {
			return false, errors.New("template document tree transaction contains a reserved operation")
		}
	}

	planID := target.TemplateDocTreePlanID
	target.TemplateDocTreePlanID = ""
	value, loaded := templateDocTreePlans.LoadAndDelete(planID)
	if !loaded {
		return false, errors.New("template document tree plan is missing or has expired")
	}
	plan, ok := value.(*templateDocTreePlan)
	if !ok || plan.id != planID || time.Now().After(plan.expiresAt) {
		return false, errors.New("template document tree plan is invalid or has expired")
	}
	if !transactionTargetsTemplateRoot(target, plan.rootID, plan.boxID) {
		return false, errors.New("template document tree plan does not match the edited document")
	}
	rootTree, loadErr := LoadTreeByBlockID(plan.rootID)
	if nil != loadErr || nil == rootTree || rootTree.Box != plan.boxID || rootTree.Path != plan.rootPath ||
		rootTree.HPath != plan.rootHPath {
		return false, errors.New("the document used to render the template has changed")
	}
	target.templateDocTreeRootSnapshot = rootTree
	box := Conf.Box(plan.boxID)
	if nil == box {
		return false, ErrBoxNotFound
	}
	for _, tree := range plan.trees {
		if nil == tree || nil == tree.Root || tree.ID != tree.Root.ID || tree.Box != plan.boxID || box.Exist(tree.Path) {
			return false, errors.New("template document tree plan contains an invalid document snapshot")
		}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Re-render the template to create a fresh plan and attach it promptly (well inside the TTL)
  2. Do not mutate TemplateDocTreePlanID between plan creation and transaction submission
  3. Check for races: ensure only one goroutine/transaction consumes a given plan ID
  4. If this recurs, file a bug with kernel logs — a wrong-typed map value is an internal invariant violation

Example fix

// before
// submitting 10 minutes after rendering; templateDocTreePlanTTL already elapsed
// after
planID, _ := renderTemplateDocTree(...)
go func() { time.Sleep(jitter); tx.TemplateDocTreePlanID = planID; performTransactions([]*Transaction{tx}) }() // submit immediately after rendering
Defensive patterns

Strategy: retry

Validate before calling

if time.Now().After(planExpiresAt) { re-render the plan before attaching }

Try / catch

if err != nil && strings.Contains(err.Error(), "plan is invalid or has expired") { tx.TemplateDocTreePlanID = reRenderPlan(); retry }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/1211bfb2604fb012. Report an issue: GitHub.