siyuan-note/siyuan · error

block swap requires two non-document blocks

Error message

block swap requires two non-document blocks

What it means

validateBlockSwap requires both the referencing block and the definition block to be real content blocks with a parent. This error means one of the nodes is nil or is a document root (no parent), which cannot take part in a block swap. Document blocks are the swap boundary and are excluded by design.

Solutions

  1. Pass block IDs (not document IDs) for both operation.ID and operation.BlockID
  2. Verify both blocks still exist (e.g. via /api/query/sql or /api/filetree/getDoc) before swapping
  3. If swapping a list item is intended, pass the item's own ID; the kernel promotes ListItem parents internally

Example fix

// before
{"action":"swap-block-ref","id":"20240101111111-abc","blockID":"20240101-doc-00-root"} // blockID is a document
// after
{"action":"swap-block-ref","id":"20240101111111-abc","blockID":"20240101222222-xyz"} // both are child blocks
Defensive patterns

Strategy: validation

Validate before calling

for (const id of [op.id, op.blockID]) {
  const node = await getNode(id); // /api/filetree/getDoc or SQL lookup
  if (!node || node.type === 'document' || node.parentId == null) throw new Error(id + ' is not a non-document block');
}

Type guard

function isSwappableBlock(node) { return node != null && node.type !== 'document' && node.parentId != null; }

Prevention

When it happens

Trigger: Calling swap-block-ref with operation.ID or operation.BlockID pointing to a document ID, to a nonexistent block ID, or to a node fetched without parent linkage.

Common situations: Using the document's ID where a block ID was expected; stale IDs left after the target block was deleted; scripts computing IDs from search results that return documents.

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/7faa36945d1a436a. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/transaction_block_swap.go:90

	if refTree.ID != defTree.ID {
		trees = append(trees, defTree)
	}
	before := captureBlockSwapFragments(trees)
	tx.saveBlockSwapOriginalTrees(trees)
	swapBlockRefNodes(ref, def, operation.BlockID, *options.IncludeChildren, *options.OriginalToEmbed)
	after := captureBlockSwapFragments(trees)
	state := newBlockSwapState(before, after)
	operation.blockSwapState = state
	operation.RetData = state.rootIDs
	tx.UndoOperations = []*Operation{{Action: "swapBlockRef", ID: operation.ID, BlockID: operation.BlockID,
		Data: operation.Data, RetData: state.rootIDs, blockSwapState: state, blockSwapUndo: true}}
	tx.finishBlockSwap(state.before, state.after, trees)
	return nil
}

func validateBlockSwap(ref, def *ast.Node, includeChildren bool) error {
	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")
	}

View on GitHub (pinned to 9f775e8a12)