siyuan-note/siyuan · error
template document tree transaction contains a reserved opera
Error message
template document tree transaction contains a reserved operation
What it means
After structural validation, AttachTemplateDocTreePlans scans every do and undo operation for the reserved actions 'restoreCreatedDoc' and 'removeCreatedDoc'. These actions are owned exclusively by the document-tree plan machinery (used for compensation/cleanup of created documents); a user-supplied transaction containing them is rejected to prevent conflicts with the plan's own generated operations.
Source
Thrown at kernel/model/template_doc_tree.go:474
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")
}
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 ||View on GitHub (pinned to 8641553a1f)
Solutions
- Remove operations with the reserved actions 'restoreCreatedDoc'/'removeCreatedDoc' from the manually built transaction
- Let the plan engine generate its own compensation operations — do not pre-seed them
- If you need cleanup semantics, rely on undo/redo of the plan transaction rather than emitting the reserved actions yourself
- Use distinct custom action names for your own operation types
Example fix
// before: reusing a reserved action
ops = append(ops, &Operation{Action: "removeCreatedDoc", ID: docID})
// after: use a non-reserved action
tx := &Transaction{TemplateDocTreePlanID: planID, DoOperations: doOps, UndoOperations: undoOps}
planEngine.Apply(tx) // engine emits its own compensation ops Defensive patterns
Strategy: validation
Validate before calling
// Go: reject reserved actions before submission
reserved := map[string]bool{"restoreCreatedDoc": true, "removeCreatedDoc": true}
for _, op := range append(tx.DoOperations, tx.UndoOperations...) {
if op != nil && reserved[op.Action] {
return fmt.Errorf("action %q is reserved for the plan engine", op.Action)
}
} Try / catch
if _, err := AttachTemplateDocTreePlans([]*Transaction{tx}); err != nil && strings.Contains(err.Error(), "reserved operation") {
return sanitizeReservedActions(tx)
} Prevention
- Treat 'restoreCreatedDoc' and 'removeCreatedDoc' as internal-only actions
- Namespace custom operation actions distinctly from plan-engine actions
- Do not copy operations from executed plan transactions into new ones
- Request cleanup by undoing the plan transaction, not by emitting reserved actions
When it happens
Trigger: Crafting a transaction whose TemplateDocTreePlanID is set and whose DoOperations/UndoOperations include an operation with Action 'restoreCreatedDoc' or 'removeCreatedDoc'; performTransactions routes it through AttachTemplateDocTreePlans and rejects it.
Common situations: Scripts or tests reusing internal compensation actions in custom transactions; copying operation objects from a previously executed plan transaction into a new one; attempting to manually trigger the cleanup actions that the plan system generates internally.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- a document tree plan must be applied in a single transaction
- template document tree plans cannot be attached to replay tr
- template document tree plan requires reversible parent opera
- attribute view not found
- ErrSpecTooNew
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/010e786b555fcb84.
Report an issue: GitHub.