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
- Pass block IDs (not document IDs) for both operation.ID and operation.BlockID
- Verify both blocks still exist (e.g. via /api/query/sql or /api/filetree/getDoc) before swapping
- 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
- Resolve block IDs from block searches, not document listings
- Validate both IDs exist and are non-document blocks before submitting
- Handle deleted-block stale IDs in automation scripts
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
- invalid block swap options
- AI editor action must not be empty
- block [ ] type is locked: expected , got
- block swap must be submitted as a separate transaction
- Bookmark cannot be empty
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)