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

  1. Swap blocks only within the same notebook, or within notebooks sharing the same crypto boundary
  2. Move one of the blocks into the other notebook first (as a normal move), then perform the swap
  3. 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

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


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)