{"record":{"id":"d405a82bc960343c","repo":"siyuan-note/siyuan","slug":"block-s-type-q-is-a-leaf-block-and-cannot-have","errorCode":null,"errorMessage":"block [%s] type %q is a leaf block and cannot have children; use previousID to place the block as its sibling instead","messagePattern":"block \\[(.+?)\\] type %q is a leaf block and cannot have children; use previousID to place the block as its sibling instead","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/treenode/blocktree.go","lineNumber":552,"sourceCode":"\n// CheckContainerParent 校验 parentID 指向的块是否允许接收子块。\n// 仅在“通过 parentID 定位插入/移动目标”（即不依赖 previousID/nextID）的场景下调用，\n// 因为一旦带 previousID/nextID，事务层走的是兄弟级 InsertAfter/InsertBefore，天然合法。\n// 返回 nil 表示合法；返回 error 时调用方应拒绝本次操作。\nfunc CheckContainerParent(parentID string) error {\n\tbt := GetBlockTree(parentID)\n\tif nil == bt {\n\t\treturn fmt.Errorf(\"parent block not found: %s\", parentID)\n\t}\n\tif IsContainerType(bt.Type) {\n\t\treturn nil\n\t}\n\tif \"h\" == bt.Type {\n\t\t// 标题是叶子块，其“子内容”在数据结构上实为后续兄弟节点（由 HeadingChildren 按层级推算）。\n\t\t// 把块挂成标题的 AST 子节点属于非法嵌套，应改用 previousID 定位。\n\t\treturn fmt.Errorf(\"heading [%s] is a leaf block and cannot have children; to place a block below this heading, pass previousID=<heading id> or previousID=<last block below the heading> instead of parentID\", parentID)\n\t}\n\treturn fmt.Errorf(\"block [%s] type %q is a leaf block and cannot have children; use previousID to place the block as its sibling instead\", parentID, bt.Type)\n}\n\n// CheckListItemNesting 校验 parentID 和 childID 是否形成“列表项直含列表项”的非法嵌套。\n// 嵌套列表的正确结构是 ListItem > List > ListItem，列表项不能直接作为另一个列表项的子块。\n// 仅在 move 场景调用（源和目标类型均已知）。\nfunc CheckListItemNesting(parentID, childID string) error {\n\tparentBt := GetBlockTree(parentID)\n\tchildBt := GetBlockTree(childID)\n\tif nil == parentBt || nil == childBt {\n\t\treturn nil // 查不到就放行，不阻塞未知场景\n\t}\n\tif \"i\" == parentBt.Type && \"i\" == childBt.Type {\n\t\treturn fmt.Errorf(\"a list-item cannot directly contain another list-item; to nest, first create a list (NodeList) under the outer list-item, then add the inner list-items to that list\")\n\t}\n\treturn nil\n}\n\nfunc SetBlockTreePath(tree *parse.Tree) {","sourceCodeStart":534,"sourceCodeEnd":570,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/treenode/blocktree.go#L534-L570","documentation":"Returned by CheckContainerParent when the resolved block is a non-heading leaf type (IsContainerType false, Type != \"h\") — e.g. a paragraph, code block, math block, table, etc. Such blocks cannot have AST children; content must be placed as a sibling.","triggerScenarios":"Calling a parentID-based insert or move with parentID set to any leaf block (paragraph, code, blockquote-content-as-leaf, etc.). Guard runs in api/block_op.go, MCP tools/block.go, cli/cmd/block.go insert/move paths.","commonSituations":"Treating a paragraph as a container; moving a block 'into' a code or math block via parentID; misreading the block type when constructing the request.","solutions":["Switch the request from parentID to previousID (or nextID) so the block is placed as a sibling.","If you intended nesting, target a real container (document, superblock, list, list-item, blockquote) instead of the leaf.","Check the block's Type via GetBlockTree before deciding parentID vs previousID."],"exampleFix":"// before\nInsertBlock(parentID: paragraphID, data: block) // -> leaf block cannot have children\n\n// after\nbt := treenode.GetBlockTree(paragraphID)\nif treenode.IsContainerType(bt.Type) {\n    InsertBlock(parentID: paragraphID, data: block)\n} else {\n    InsertBlock(previousID: paragraphID, data: block)\n}","handlingStrategy":"validation","validationCode":"// Choose parent vs sibling placement based on whether the target is a container.\nbt := treenode.GetBlockTree(parentID)\nif bt == nil {\n    return fmt.Errorf(\"parent %q not found\", parentID)\n}\nif !treenode.IsContainerType(bt.Type) {\n    return fmt.Errorf(\"block %s is a leaf (%s); use previousID to place as sibling\", parentID, bt.Type)\n}\nreturn nil","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Resolve the target block's Type before deciding between parentID and previousID.","Only document, superblock, list, list-item, and blockquote blocks accept children.","Default to previousID when the target type is unknown."],"tags":["block","leaf","nesting","validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}