siyuan-note/siyuan · error
template document tree plan parent operations are not revers
Error message
template document tree plan parent operations are not reversible
What it means
Reversibility check: for each DoOperation, an UndoOperation whose action is the inverse (key = inverseAction + NUL + ID) must exist; afterwards no leftover undo operations may remain (the trailing loop reports the same message). This error means the Do and Undo lists are not exact inverses of each other — an operation has no matching inverse, or inverses are missing/duplicated so counts never balance to zero.
Source
Thrown at kernel/model/template_doc_tree.go:561
}
undoOperations[operation.Action+"\x00"+operation.ID]++
}
hasContentMutation := false
for _, operation := range transaction.DoOperations {
if nil == operation || "" == operation.ID {
return errors.New("template document tree plan contains an invalid parent operation")
}
inverseAction, supported := inverseActions[operation.Action]
if !supported || "" != operation.RootID {
return errors.New("template document tree plan contains an unsupported parent operation")
}
if "insert" == operation.Action || "delete" == operation.Action || "update" == operation.Action {
hasContentMutation = true
}
key := inverseAction + "\x00" + operation.ID
if 1 > undoOperations[key] {
return errors.New("template document tree plan parent operations are not reversible")
}
undoOperations[key]--
}
if !hasContentMutation {
return errors.New("template document tree plan requires a parent content operation")
}
for _, count := range undoOperations {
if 0 != count {
return errors.New("template document tree plan parent operations are not reversible")
}
}
return nil
}
func transactionTargetsTemplateRoot(transaction *Transaction, rootID, boxID string) bool {
matched := false
for operationSetIndex, operations := range [][]*Operation{transaction.DoOperations, transaction.UndoOperations} {
for _, operation := range operations {View on GitHub (pinned to 8641553a1f)
Solutions
- For every DoOperation, emit an UndoOperation with the inverse action (insert<->delete, update<->update, foldHeading<->unfoldHeading, setAttrs<->setAttrs) and the same ID
- Ensure the Do and Undo lists are exact mirrors with no extra or missing entries so counters balance to zero
- Include at least one content mutation (insert, delete, or update) — fold/attr-only pairs are insufficient
- Generate both lists programmatically from the same operation set instead of writing them by hand
Example fix
// before
doOps := []*Operation{{Action: "insert", ID: blkID, ParentID: p}}
undoOps := []*Operation{} // no inverse
// after
doOps := []*Operation{{Action: "insert", ID: blkID, ParentID: p}}
undoOps := []*Operation{{Action: "delete", ID: blkID}} // inverse with same ID Defensive patterns
Strategy: validation
Validate before calling
inverse := map[string]string{"insert":"delete","delete":"insert","update":"update","foldHeading":"unfoldHeading","unfoldHeading":"foldHeading","setAttrs":"setAttrs"}; need := map[string]int{}; for _, op := range tx.DoOperations { need[inverse[op.Action]+"\x00"+op.ID]++ }; got := map[string]int{}; for _, op := range tx.UndoOperations { got[op.Action+"\x00"+op.ID]++ }; for k, n := range need { if got[k] < n { reject: not reversible } } Try / catch
if err != nil && strings.Contains(err.Error(), "parent operations are not reversible") { rebuild the undo list programmatically as the exact inverse of the do list and resubmit } Prevention
- Generate UndoOperations as the exact mirror of DoOperations from one source of truth
- Pair every do op with its inverse action and identical ID
- Include at least one insert/delete/update pair in the parent transaction
- Add a client-side balance check (counts must net to zero) before submission
When it happens
Trigger: DoOperations with fewer UndoOperations than needed; mismatched action pairs (e.g. insert forward with setAttrs undo); duplicate or extra undo operations left after all do ops are matched (leftover count != 0 hits the second identical error at line 570); the transaction lacks an insert/delete/update pair so hasContentMutation also triggers the related 'requires a parent content operation' error.
Common situations: Manually constructed transactions where the undo list was truncated or reordered incorrectly; clients that generate undo ops only for some actions; tests with asymmetric fixtures.
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
- template document tree plan contains an invalid parent undo
- template document tree plan contains an unsupported parent u
- template document tree plan contains an invalid parent opera
- template document tree plan contains an unsupported parent o
- wrong layout type
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/87b2323f4eb96261.
Report an issue: GitHub.