siyuan-note/siyuan · error

cannot swap a block with itself or its ancestor

Error message

cannot swap a block with itself or its ancestor

What it means

The two blocks to swap have an ancestor/descendant relationship (or are the same block). Swapping a block with itself or with something that contains it would create a cycle in the tree, so validateBlockSwap refuses it via the contains() helper that walks each node's ancestors.

Solutions

  1. Pick two sibling-level blocks that do not contain each other
  2. If a parent-child swap is intended, restructure as a move operation instead of a swap
  3. Check that the ref ID and def ID differ and neither is an ancestor of the other before submitting

Example fix

// before
// def block is a child of ref block -> rejected
// after
// choose two blocks with no containment, e.g. two sibling paragraphs
{"action":"swap-block-ref","id":"<paragraphA>","blockID":"<paragraphB>"}
Defensive patterns

Strategy: validation

Validate before calling

if (op.id === op.blockID) throw new Error('cannot swap a block with itself');
if (isAncestor(op.id, op.blockID) || isAncestor(op.blockID, op.id)) throw new Error('ancestor/descendant swap not allowed');

Prevention

When it happens

Trigger: Calling swap-block-ref with operation.ID equal to operation.BlockID; or one block ID being a heading/parent block that contains the other (e.g. swapping a container/parent block with one of its children).

Common situations: Selecting a whole outline/heading section and asking to swap with a block inside it; recursive or scripted swaps where IDs were computed from the same node; UI drag logic passing the drop target's ancestor.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/6df5a8d6e2cc622c. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/transaction_block_swap.go:107

	if ref == nil || def == nil || ref.Parent == nil || def.Parent == nil {
		return errors.New("block swap requires two non-document blocks")
	}
	if ref.Parent.Type == ast.NodeListItem {
		ref = ref.Parent
	}
	if def.Parent.Type == ast.NodeListItem {
		def = def.Parent
	}
	contains := func(parent, node *ast.Node) bool {
		for ; node != nil; node = node.Parent {
			if node == parent {
				return true
			}
		}
		return false
	}
	if contains(ref, def) || contains(def, ref) {
		return errors.New("cannot swap a block with itself or its ancestor")
	}
	if includeChildren && def.Type == ast.NodeHeading {
		for _, child := range treenode.HeadingChildren(def) {
			if contains(child, ref) {
				return errors.New("cannot swap a heading with a block in its section")
			}
		}
	}
	return nil
}

func captureBlockSwapFragments(trees []*parse.Tree) (ret []blockSwapFragment) {
	for _, tree := range trees {
		var previousID string
		for node := tree.Root.FirstChild; node != nil; node = node.Next {
			if !node.IsBlock() || node.ID == "" {
				continue
			}

View on GitHub (pinned to 9f775e8a12)