siyuan-note/siyuan · error

invalid block structure: %s [%s] is not a content block

Error message

invalid block structure: %s [%s] is not a content block

What it means

Returned by invalidBlockNodeError in kernel/treenode/block_structure.go when ValidateBlockSubtree / ValidateBlockPlacement / ValidateBlockReplacement receives a node that is not a content block — i.e. isContentBlock returns false because the node is nil, is not a block (node.IsBlock() false), or is a NodeKramdownBlockIAL node (the IAL marker, not real content).

Source

Thrown at kernel/treenode/block_structure.go:107

		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

  1. Guard with isContentBlock (or node != nil && node.IsBlock() && node.Type != ast.NodeKramdownBlockIAL) before calling validate.
  2. Make sure you are passing a block-level node (paragraph, heading, list, list-item, blockquote, code block, document, etc.), not an inline or IAL node.
  3. If the node came from JSON, verify the Type field maps to an ast.NodeType that IsBlock() returns true for.

Example fix

// before
err := treenode.ValidateBlockSubtree(ialNode) // ialNode.Type == ast.NodeKramdownBlockIAL

// after
if node != nil && node.IsBlock() && node.Type != ast.NodeKramdownBlockIAL {
    err = treenode.ValidateBlockSubtree(node)
}
Defensive patterns

Strategy: type-guard

Type guard

func isContentBlock(n *ast.Node) bool {
    return n != nil && n.IsBlock() && n.Type != ast.NodeKramdownBlockIAL
}

// usage
if !isContentBlock(node) {
    return fmt.Errorf("node is nil, inline, or an IAL marker; cannot validate as a block")
}
err := treenode.ValidateBlockSubtree(node)

Prevention

When it happens

Trigger: Passing a nil node, an inline (non-block) node, or a NodeKramdownBlockIAL node as the root argument to any treenode.ValidateBlock* function. Typically happens in code that walks the raw AST and hands the wrong node type to validation.

Common situations: A plugin/agent picks an inline span or the IAL attribute node out of the tree and tries to validate it as a block; a nil check is missing before calling validate; deserialised JSON node whose Type was not set to a block type.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/70f124eff4cafedf. Report an issue: GitHub.