siyuan-note/siyuan · error

template document tree plan contains an invalid parent opera

Error message

template document tree plan contains an invalid parent operation

What it means

The mirror check for forward operations: every DoOperation in the plan transaction must be non-nil with a non-empty ID. This error means the transaction's forward (do) operations include a nil entry or an operation lacking a block ID, so no inverse operation can ever be matched for it.

Source

Thrown at kernel/model/template_doc_tree.go:550

		"foldHeading":   "unfoldHeading",
		"unfoldHeading": "foldHeading",
		"setAttrs":      "setAttrs",
	}
	undoOperations := map[string]int{}
	for _, operation := range transaction.UndoOperations {
		if nil == operation || "" == operation.ID {
			return errors.New("template document tree plan contains an invalid parent undo operation")
		}
		if _, supported := inverseActions[operation.Action]; !supported || "" != operation.RootID {
			return errors.New("template document tree plan contains an unsupported parent undo operation")
		}
		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 {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Set Operation.ID on every DoOperation (the block being inserted/updated/deleted/folded)
  2. Filter out nil entries from DoOperations before submission
  3. Validate all forward operations client-side before calling the transactions endpoint
  4. Regenerate the transaction using the current kernel client library instead of hand-assembling it

Example fix

// before
doOps := []*Operation{{Action: "insert", ParentID: parentID}} // no ID
// after
doOps := []*Operation{{Action: "insert", ID: newBlockID, ParentID: parentID}}
Defensive patterns

Strategy: validation

Validate before calling

for i, op := range tx.DoOperations { if op == nil || op.ID == "" { return fmt.Errorf("do operation %d missing ID", i) } }

Type guard

func validDoOps(ops []*Operation) bool { for _, op := range ops { if op == nil || op.ID == "" { return false } }; return len(ops) > 0 }

Try / catch

if err != nil && strings.Contains(err.Error(), "invalid parent operation") { fix the client builder to assign Operation.ID on every do op, then resubmit }

Prevention

When it happens

Trigger: Hand-building a plan transaction with a nil or ID-less DoOperation; a client bug that appends placeholder operations; deserialization producing empty Operation.ID for some entries.

Common situations: Custom automation scripts calling the transactions API with partially filled operations; test fixtures missing IDs; older client formats that omit IDs for some actions.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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