{"record":{"id":"70f124eff4cafedf","repo":"siyuan-note/siyuan","slug":"invalid-block-structure-s-s-is-not-a-content","errorCode":null,"errorMessage":"invalid block structure: %s [%s] is not a content block","messagePattern":"invalid block structure: (.+?) \\[(.+?)\\] is not a content block","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/treenode/block_structure.go","lineNumber":107,"sourceCode":"\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":89,"sourceCodeEnd":109,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/treenode/block_structure.go#L89-L109","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Guard with isContentBlock (or node != nil && node.IsBlock() && node.Type != ast.NodeKramdownBlockIAL) before calling validate.","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.","If the node came from JSON, verify the Type field maps to an ast.NodeType that IsBlock() returns true for."],"exampleFix":"// before\nerr := treenode.ValidateBlockSubtree(ialNode) // ialNode.Type == ast.NodeKramdownBlockIAL\n\n// after\nif node != nil && node.IsBlock() && node.Type != ast.NodeKramdownBlockIAL {\n    err = treenode.ValidateBlockSubtree(node)\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isContentBlock(n *ast.Node) bool {\n    return n != nil && n.IsBlock() && n.Type != ast.NodeKramdownBlockIAL\n}\n\n// usage\nif !isContentBlock(node) {\n    return fmt.Errorf(\"node is nil, inline, or an IAL marker; cannot validate as a block\")\n}\nerr := treenode.ValidateBlockSubtree(node)","tryCatchPattern":null,"preventionTips":["Never pass inline nodes (text spans, marks) or NodeKramdownBlockIAL nodes to treenode.ValidateBlock*.","Filter walks with node.IsBlock() before validation.","When deserialising nodes from JSON, assert the Type resolves to a block-level NodeType."],"tags":["block","ast","validation","type-guard"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}