{"record":{"id":"3f1a02ccc06f7178","repo":"siyuan-note/siyuan","slug":"block-changed-since-conversion-s","errorCode":null,"errorMessage":"block changed since conversion: %s","messagePattern":"block changed since conversion: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/transaction_block_swap.go","lineNumber":231,"sourceCode":"\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}\n\t\tnode := treenode.GetNodeInTree(tree, fragment.node.ID)\n\t\tif node == nil || node.Parent != tree.Root || blockSwapFingerprint(node) != blockSwapFingerprint(fragment.node) {\n\t\t\treturn fmt.Errorf(\"block changed since conversion: %s\", fragment.node.ID)\n\t\t}\n\t\tpreviousID, nextID := \"\", \"\"\n\t\tfor previous := node.Previous; previous != nil; previous = previous.Previous {\n\t\t\tif previous.IsBlock() && previous.ID != \"\" {\n\t\t\t\tpreviousID = previous.ID\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tfor next := node.Next; next != nil; next = next.Next {\n\t\t\tif next.IsBlock() && next.ID != \"\" {\n\t\t\t\tnextID = next.ID\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif previousID != fragment.previousID || nextID != fragment.nextID {\n\t\t\treturn fmt.Errorf(\"block position changed since conversion: %s\", fragment.node.ID)\n\t\t}\n\t\tfor _, id := range node.BlockIDs() {","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/transaction_block_swap.go#L213-L249","documentation":"Before restoring swapped fragments, the kernel fingerprints each stored node (blockSwapFingerprint) and compares it with the current node found in the tree, also verifying the node still exists and is still a direct child of the document root. This error means the target block has been edited, moved, or deleted since the block swap state was captured, so blindly restoring would overwrite newer user content.","triggerScenarios":"Undoing/redoing a swap after the fragment block's text was edited; the block was moved out of the document root (Parent != tree.Root); the block ID no longer resolves in the tree (node == nil).","commonSituations":"Editing a paragraph then undoing an earlier block swap; sync merging changes from another device between the swap and the replay; plugins modifying blocks after the swap state snapshot.","solutions":["Accept the newer content and drop the stale swap/undo state","Revert the recent edit to the block so it matches the swap-time content, then retry undo/redo","Re-sync the workspace so both devices have consistent block content before replaying"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const node = findNode(tree, fragmentNodeId);\nif (!node || node.parentId !== tree.rootId || fingerprint(node) !== fragment.fingerprint) {\n  throw new Error('block ' + fragmentNodeId + ' changed since conversion');\n}","typeGuard":"function isUnchangedSwapNode(tree, frag) {\n  const node = tree && frag && frag.node && frag.node.id ? findNode(tree, frag.node.id) : null;\n  return node != null && node.parentId === tree.rootId && fingerprint(node) === fingerprint(frag.node);\n}","tryCatchPattern":"try { await replaySwap(tx); } catch (e) {\n  if (e.message.startsWith('block changed since conversion')) { /* keep newer content; drop stale undo state */ }\n}","preventionTips":["Avoid editing blocks between a swap and its undo/redo","Complete pending swap replays before syncing changes from other devices"],"tags":["replay","block-swap","conflict","fingerprint"],"backgroundTag":"checksum-mismatch","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"}