siyuan-note/siyuan · error
invalid block structure: %s [%s] cannot contain %s [%s]
Error message
invalid block structure: %s [%s] cannot contain %s [%s]
What it means
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).
Source
Thrown at kernel/treenode/block_structure.go:99
return invalidBlockNodeError(newNode)
}
parent := oldNode.Parent
for nil != parent && !isContentBlock(parent) {
parent = parent.Parent
}
if nil != parent && !CanContainBlock(parent.Type, newNode.Type) {
return invalidBlockContainmentError(parent, newNode)
}
return ValidateBlockSubtree(newNode)
}
func isContentBlock(node *ast.Node) bool {
return nil != node && node.IsBlock() && ast.NodeKramdownBlockIAL != node.Type
}
func invalidBlockContainmentError(parent, child *ast.Node) error {
return fmt.Errorf("invalid block structure: %s [%s] cannot contain %s [%s]",
parent.Type.String(), parent.ID, child.Type.String(), child.ID)
}
func invalidBlockNodeError(node *ast.Node) error {
if nil == node {
return fmt.Errorf("invalid block structure: block node is nil")
}
return fmt.Errorf("invalid block structure: %s [%s] is not a content block", node.Type.String(), node.ID)
}
View on GitHub (pinned to 251596fc0d)
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.
Example fix
// before: paragraph set as child of another paragraph
parent := &ast.Node{Type: ast.NodeParagraph}
child := treenode.NewParagraph("")
parent.AppendChild(child)
err := treenode.ValidateBlockPlacement(child) // -> cannot contain
// after: use a document/container root, or sibling placement
root := &ast.Node{Type: ast.NodeDocument}
root.AppendChild(child)
err := treenode.ValidateBlockPlacement(child) Defensive patterns
Strategy: validation
Validate before calling
// Validate containment before inserting using the same rule the validator applies.
func canPlace(parent, child *ast.Node) error {
if parent == nil || !parent.IsBlock() || parent.Type == ast.NodeKramdownBlockIAL {
return fmt.Errorf("parent is not a content block")
}
if !treenode.CanContainBlock(parent.Type, child.Type) {
return fmt.Errorf("parent %s cannot contain child %s", parent.Type, child.Type)
}
return nil
}
if err := canPlace(parentNode, childNode); err != nil { return err } Type guard
func isValidContentBlock(n *ast.Node) bool {
return n != nil && n.IsBlock() && n.Type != ast.NodeKramdownBlockIAL
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- invalid block structure: %s [%s] is not a content block
- --id is required
- tree is empty
- invalid path
- block [%s] is not an instance of attribute view [%s]
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/9c086c57710e9298.
Report an issue: GitHub.