{"record":{"id":"660596d008d6222c","repo":"siyuan-note/siyuan","slug":"block-s-type-is-locked-expected-s-got-s","errorCode":null,"errorMessage":"block [%s] type is locked: expected %s, got %s","messagePattern":"block \\[(.+?)\\] type is locked: expected (.+?), got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/block_update.go","lineNumber":316,"sourceCode":"}\n\nfunc firstContentBlock(parent *ast.Node) *ast.Node {\n\tif nil == parent {\n\t\treturn nil\n\t}\n\tfor child := parent.FirstChild; nil != child; child = child.Next {\n\t\tif child.IsBlock() && ast.NodeKramdownBlockIAL != child.Type {\n\t\t\treturn child\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validateBlockUpdateType(oldNode, updatedNode *ast.Node, lockType bool) error {\n\tif !lockType || oldNode.Type == updatedNode.Type || isEmptyParagraphBlock(oldNode) {\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"block [%s] type is locked: expected %s, got %s\",\n\t\toldNode.ID, oldNode.Type.String(), updatedNode.Type.String())\n}\n\nfunc isEmptyParagraphBlock(node *ast.Node) bool {\n\tif nil == node || ast.NodeParagraph != node.Type {\n\t\treturn false\n\t}\n\tfor child := node.FirstChild; nil != child; child = child.Next {\n\t\tswitch child.Type {\n\t\tcase ast.NodeText:\n\t\t\ttext := strings.ReplaceAll(string(child.Tokens), \"\\u200b\", \"\")\n\t\t\tif \"\" != strings.TrimSpace(text) {\n\t\t\t\treturn false\n\t\t\t}\n\t\tcase ast.NodeSoftBreak, ast.NodeBr:\n\t\tdefault:\n\t\t\treturn false\n\t\t}","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/block_update.go#L298-L334","documentation":"validateBlockUpdateType enforces the LockType option of the block update API: when lockType is true, the replacement block must keep the same node type as the original (empty paragraph blocks are exempt since they carry no meaningful type). If the type changes, the update is rejected with a message showing the block ID, expected type, and actual type. This prevents an update from silently converting e.g. a paragraph into a heading.","triggerScenarios":"Calling buildBlockUpdateOperations / doUpdate (the updateBlock API) with LockType=true while the new payload parses to a different block type than the existing block — e.g. replacing a NodeParagraph with a NodeHeading, or a paragraph with a code block.","commonSituations":"Clients that pass lockType=true out of habit while sending structurally different content; plugins updating a block's content but accidentally changing its element type; automation converting block types when only text edit was intended.","solutions":["Set lockType=false (or omit it) if changing the block type is intended","Keep the replacement payload's outermost block type identical to the existing block (e.g. wrap content in a paragraph node when updating a paragraph)","Use delete + insert operations when the block type must change","Match the message's expected/got types to adjust the payload accordingly"],"exampleFix":"// before\n{\"id\": \"...\", \"dataType\": \"markdown\", \"data\": \"# heading text\", \"lockType\": true} // updating a paragraph\n// after\n{\"id\": \"...\", \"dataType\": \"markdown\", \"data\": \"heading text\", \"lockType\": true}","handlingStrategy":"validation","validationCode":"// only send lockType=true when the outermost block type is unchanged\nif (payload.lockType && outerBlockTypeOf(payload.data) !== currentBlockType) {\n  payload.lockType = false; // or transform the payload\n}","typeGuard":"const sameType = (oldT, newT) => oldT === newT || oldT === \"NodeParagraph\";","tryCatchPattern":"try {\n  await updateBlock(payload);\n} catch (e) {\n  if (String(e.msg).includes(\"type is locked\")) {\n    // retry with lockType=false or fix the payload's block type\n  }\n}","preventionTips":["Only enable lockType when the update preserves block type","Parse the expected/got types from the error message to fix payloads","Use delete + insert when a type change is required"],"tags":["block-update","type-mismatch","api","validation"],"backgroundTag":"type-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"}