{"record":{"id":"9c086c57710e9298","repo":"siyuan-note/siyuan","slug":"invalid-block-structure-s-s-cannot-contain-s","errorCode":null,"errorMessage":"invalid block structure: %s [%s] cannot contain %s [%s]","messagePattern":"invalid block structure: (.+?) \\[(.+?)\\] cannot contain (.+?) \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/treenode/block_structure.go","lineNumber":99,"sourceCode":"\t\treturn invalidBlockNodeError(newNode)\n\t}\n\n\tparent := oldNode.Parent\n\tfor nil != parent && !isContentBlock(parent) {\n\t\tparent = parent.Parent\n\t}\n\tif nil != parent && !CanContainBlock(parent.Type, newNode.Type) {\n\t\treturn invalidBlockContainmentError(parent, newNode)\n\t}\n\treturn ValidateBlockSubtree(newNode)\n}\n\nfunc isContentBlock(node *ast.Node) bool {\n\treturn nil != node && node.IsBlock() && ast.NodeKramdownBlockIAL != node.Type\n}\n\nfunc invalidBlockContainmentError(parent, child *ast.Node) error {\n\treturn fmt.Errorf(\"invalid block structure: %s [%s] cannot contain %s [%s]\",\n\t\tparent.Type.String(), parent.ID, child.Type.String(), child.ID)\n}\n\nfunc invalidBlockNodeError(node *ast.Node) error {\n\tif nil == node {\n\t\treturn fmt.Errorf(\"invalid block structure: block node is nil\")\n\t}\n\treturn fmt.Errorf(\"invalid block structure: %s [%s] is not a content block\", node.Type.String(), node.ID)\n}\n","sourceCodeStart":81,"sourceCodeEnd":109,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/treenode/block_structure.go#L81-L109","documentation":"Returned by invalidBlockContainmentError in kernel/treenode/block_structure.go. It fires from ValidateBlockPlacement, ValidateBlockReplacement, and ValidateBlockSubtree when CanContainBlock(parent.Type, child.Type) is false — meaning the Lute AST rule parent.CanContain(childType) rejects the parent→child combination (e.g. a paragraph holding another block, or a container type that cannot hold this specific child type).","triggerScenarios":"Constructing or mutating a block subtree (insert, move, replace) where a child block type is placed under a parent block type that the Lute AST does not allow to contain it. Reached via treenode.ValidateBlockPlacement / ValidateBlockReplacement / ValidateBlockSubtree, which guard block-op code paths including the HTTP block API, MCP block tools, and CLI block commands.","commonSituations":"Programmatic block building in a plugin or agent that manually assembles ast.Node trees; moving a block under a parent whose type was checked against the blocktree but whose AST-level containment rule is stricter; replacing a block with a new node of an incompatible category.","solutions":["Inspect the parent.Type and child.Type in the error message and consult CanContainBlock to find a legal parent type.","Insert an intermediate container block (e.g. a list under a list-item) so the parent→child pair becomes valid.","When moving, prefer previousID/nextID sibling placement which the transaction layer handles as InsertAfter/InsertBefore and skips this check."],"exampleFix":"// before: paragraph set as child of another paragraph\nparent := &ast.Node{Type: ast.NodeParagraph}\nchild := treenode.NewParagraph(\"\")\nparent.AppendChild(child)\nerr := treenode.ValidateBlockPlacement(child) // -> cannot contain\n\n// after: use a document/container root, or sibling placement\nroot := &ast.Node{Type: ast.NodeDocument}\nroot.AppendChild(child)\nerr := treenode.ValidateBlockPlacement(child)","handlingStrategy":"validation","validationCode":"// Validate containment before inserting using the same rule the validator applies.\nfunc canPlace(parent, child *ast.Node) error {\n    if parent == nil || !parent.IsBlock() || parent.Type == ast.NodeKramdownBlockIAL {\n        return fmt.Errorf(\"parent is not a content block\")\n    }\n    if !treenode.CanContainBlock(parent.Type, child.Type) {\n        return fmt.Errorf(\"parent %s cannot contain child %s\", parent.Type, child.Type)\n    }\n    return nil\n}\n\nif err := canPlace(parentNode, childNode); err != nil { return err }","typeGuard":"func isValidContentBlock(n *ast.Node) bool {\n    return n != nil && n.IsBlock() && n.Type != ast.NodeKramdownBlockIAL\n}","tryCatchPattern":null,"preventionTips":["Always call treenode.CanContainBlock(parentType, childType) before appending a child in code that builds AST trees.","Prefer sibling-based placement (previousID/nextID) when unsure of container rules.","Run treenode.ValidateBlockSubtree on any programmatically built subtree before committing it."],"tags":["block","ast","tree","structure","validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}