{"record":{"id":"3c74af731a26b3c8","repo":"siyuan-note/siyuan","slug":"cannot-replay-block-swap-across-encrypted-notebook","errorCode":null,"errorMessage":"cannot replay block swap across encrypted notebook boundaries","messagePattern":"cannot replay block swap across encrypted notebook boundaries","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/transaction_block_swap.go","lineNumber":210,"sourceCode":"func (tx *Transaction) replayBlockSwap(operation *Operation) error {\n\tstate := operation.blockSwapState\n\tfrom, to := state.before, state.after\n\tif operation.blockSwapUndo {\n\t\tfrom, to = to, from\n\t}\n\ttrees := map[string]*parse.Tree{}\n\tvar orderedTrees []*parse.Tree\n\tfor _, rootID := range state.rootIDs {\n\t\ttree, err := tx.loadTree(rootID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttrees[rootID] = tree\n\t\torderedTrees = append(orderedTrees, tree)\n\t}\n\tfor _, tree := range orderedTrees {\n\t\tif !IsSameCryptoBoundary(orderedTrees[0].Box, tree.Box) {\n\t\t\treturn errors.New(\"cannot replay block swap across encrypted notebook boundaries\")\n\t\t}\n\t}\n\ttx.saveBlockSwapOriginalTrees(orderedTrees)\n\tif err := restoreBlockSwapFragments(from, to, trees); err != nil {\n\t\treturn err\n\t}\n\ttx.finishBlockSwap(from, to, orderedTrees)\n\toperation.RetData = state.rootIDs\n\treturn nil\n}\n\nfunc restoreBlockSwapFragments(from, to []blockSwapFragment, trees map[string]*parse.Tree) error {\n\tremoving := map[string]bool{}\n\tfor _, fragment := range from {\n\t\ttree := trees[fragment.rootID]\n\t\tif tree == nil || tree.Box != fragment.boxID {\n\t\t\treturn errors.New(\"block swap notebook has changed\")\n\t\t}","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/transaction_block_swap.go#L192-L228","documentation":"During block swap replay (undo/redo), the kernel re-collects the trees touched by the stored fragments and re-verifies that every tree's notebook is on the same crypto boundary as the first one. This error means the replay state now spans notebooks with incompatible encryption boundaries — typically because a block or tree was moved to a different notebook between the original swap and the replay.","triggerScenarios":"Undoing/redoing a block swap after one of the involved trees was moved to a notebook with a different crypto boundary; replaying synced state where notebooks differ in encryption across clients.","commonSituations":"Moving a swapped block to another notebook, then pressing undo; mixing encrypted and plain notebooks in a synced workspace; restoring a partial snapshot.","solutions":["Restore the involved blocks to their original notebooks, then retry undo/redo","If the new location is intended, discard the undo history for that swap instead of replaying it","Align notebook encryption settings across clients and re-sync"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const boxes = touchedTrees.map(t => t.box);\nif (new Set(boxes).size > 1 && !sameCryptoBoundary(boxes)) throw new Error('replay would cross encrypted notebook boundary');","typeGuard":null,"tryCatchPattern":"try { await replay(tx); } catch (e) {\n  if (e.message.includes('cannot replay block swap across encrypted notebook boundaries')) { /* drop undo entry or restore notebooks first */ }\n}","preventionTips":["Avoid moving swap-involved blocks between differently-encrypted notebooks while undo entries are pending","Keep notebook encryption uniform across synced clients"],"tags":["encryption","replay","undo","notebook"],"backgroundTag":"incompatible-source-type","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}