siyuan-note/siyuan · error

template document tree plans cannot be attached to replay tr

Error message

template document tree plans cannot be attached to replay transactions

What it means

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.

Source

Thrown at kernel/model/template_doc_tree.go:464

}

// AttachTemplateDocTreePlans 将一次性计划转换为内核事务操作,父文档内容与全部子文档共用一条撤销记录。
func AttachTemplateDocTreePlans(transactions []*Transaction) (attached bool, err error) {
	var target *Transaction
	for _, transaction := range transactions {
		if nil == transaction || "" == transaction.TemplateDocTreePlanID {
			continue
		}
		if nil != target || 1 != len(transactions) {
			return false, errors.New("a document tree plan must be applied in a single transaction")
		}
		target = transaction
	}
	if nil == target {
		return false, nil
	}
	if target.isReplay {
		return false, errors.New("template document tree plans cannot be attached to replay transactions")
	}
	if 0 == len(target.DoOperations) || 0 == len(target.UndoOperations) {
		return false, errors.New("template document tree plan requires reversible parent operations")
	}
	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")

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Clear TemplateDocTreePlanID from a transaction before re-submitting or replaying it (AttachTemplateDocTreePlans already does this on the target before persisting)
  2. Re-issue the template document tree creation as a fresh transaction instead of replaying the old one
  3. If replaying captured transactions, strip plan metadata first and re-attach via the normal render flow
  4. Exclude template-plan transactions from any custom sync/replay pipeline

Example fix

// before: replaying a stored transaction as-is
replayTx := storedTx
performTransactions([]*Transaction{replayTx})
// after: strip plan metadata before replay
storedTx.TemplateDocTreePlanID = ""
storedTx.isReplay = false
performTransactions([]*Transaction{storedTx})
Defensive patterns

Strategy: validation

Validate before calling

// Go: refuse to replay transactions that still carry plan metadata
if tx.isReplay && tx.TemplateDocTreePlanID != "" {
    return errors.New("strip TemplateDocTreePlanID before replay")
}

Try / catch

if _, err := AttachTemplateDocTreePlans([]*Transaction{tx}); err != nil && strings.Contains(err.Error(), "replay") {
    tx.TemplateDocTreePlanID = ""
    return performTransactions([]*Transaction{tx})
}

Prevention

When it happens

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

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

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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