siyuan-note/siyuan · error · TxErr
cannot swap blocks across encrypted notebook boundaries
Error message
cannot swap blocks across encrypted notebook boundaries
What it means
The two blocks being swapped live in different notebooks, and those notebooks sit on opposite sides of the encrypted-notebook crypto boundary (IsSameCryptoBoundary returns false). Swapping fragments across such a boundary is refused to avoid writing plaintext-encrypted content between differently encrypted data stores. This is an intentional data-safety guard.
Solutions
- Swap blocks only within the same notebook, or within notebooks sharing the same crypto boundary
- Move one of the blocks into the other notebook first (as a normal move), then perform the swap
- Check notebook encryption settings and align them if cross-notebook swaps are required
Example fix
// before
{"action":"swap-block-ref","id":"20240101111111-abc (boxA: encrypted)","blockID":"20240101222222-xyz (boxB: plain)"}
// after
// move the block into the same notebook first, then:
{"action":"swap-block-ref","id":"20240101111111-abc (boxA)","blockID":"20240101333333-def (boxA)"} Defensive patterns
Strategy: validation
Validate before calling
const refBox = getBoxOfBlock(op.id), defBox = getBoxOfBlock(op.blockID);
if (refBox !== defBox && notebookEncryptionMode(refBox) !== notebookEncryptionMode(defBox)) {
throw new Error('cross encrypted notebook boundary swap is not allowed');
} Prevention
- Swap only within the same notebook unless all involved notebooks share the same crypto boundary
- Check notebook encryption configuration before automated cross-notebook operations
When it happens
Trigger: Calling a swap-block-ref transaction where operation.ID (ref block) and operation.BlockID (definition block) belong to notebooks with incompatible crypto boundaries, e.g. one encrypted notebook and one plain notebook.
Common situations: Dragging a block from an encrypted notebook into a regular one and swapping block references; scripts automating cross-notebook block moves; synced workspaces mixing encrypted and unencrypted notebooks.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- Conf.Language(314)
- path belongs to encrypted notebook
- Access to encrypted notebook data is not supported via this…
- cannot replay block swap across encrypted notebook…
- CLI does not support encrypted notebook
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/be9fbc0f4cf74a2c.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/transaction_block_swap.go:64
}
data, err := json.Marshal(operation.Data)
if err != nil {
return fail(err)
}
var options blockSwapOptions
if err = json.Unmarshal(data, &options); err != nil || options.IncludeChildren == nil || options.OriginalToEmbed == nil {
return fail(errors.New("invalid block swap options"))
}
refTree, err := tx.loadTree(operation.ID)
if err != nil {
return fail(err)
}
defTree, err := tx.loadTree(operation.BlockID)
if err != nil {
return fail(err)
}
if !IsSameCryptoBoundary(refTree.Box, defTree.Box) {
return fail(errors.New("cannot swap blocks across encrypted notebook boundaries"))
}
ref := treenode.GetNodeInTree(refTree, operation.ID)
def := treenode.GetNodeInTree(defTree, operation.BlockID)
if err = validateBlockSwap(ref, def, *options.IncludeChildren); err != nil {
return fail(err)
}
trees := []*parse.Tree{refTree}
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,View on GitHub (pinned to 9f775e8a12)