siyuan-note/siyuan · error
invalid block structure: block node is nil
Error message
invalid block structure: block node is nil
What it means
invalidBlockNodeError reports that the node handed to the block-structure validator is nil. The validator requires concrete AST nodes to check placement/replacement/subtree validity, and a nil node means the caller resolved a block ID or tree position that does not exist or was not loaded.
Source
Thrown at kernel/treenode/block_structure.go:242
item.ListData.Num = num
item.ListData.Delimiter = delimiter
item.ListData.Marker = []byte(strconv.Itoa(num) + string(delimiter))
num++
}
}
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 8641553a1f)
Solutions
- Re-resolve the block ID via GetBlockTree / tree load and confirm it exists before validating.
- Check for concurrent modifications: reload the tree and retry the operation.
- Add a nil check on the node before calling validation so a clear caller-side error is raised.
Example fix
// before
node := findNode(id) // may be nil
if err := treenode.ValidateBlockPlacement(parent, node); err != nil { ... }
// after
node := findNode(id)
if node == nil {
return fmt.Errorf("block %s not found in loaded tree", id)
}
if err := treenode.ValidateBlockPlacement(parent, node); err != nil { ... } Defensive patterns
Strategy: type-guard
Validate before calling
if (node == null) { throw new Error('block ' + id + ' not found; reload the tree before validating') } Type guard
function isResolvedNode(n) { return n != null && typeof n.ID === 'string' && n.ID.length > 0 } Try / catch
try { validateSubtree(node) } catch (e) { if (String(e.message).includes('block node is nil')) { const fresh = reloadNode(id); if (fresh) validateSubtree(fresh) } else { throw e } } Prevention
- Re-resolve block IDs after any concurrent edit
- Never cache *ast.Node pointers across tree reloads
- Null-check node lookups before passing to validators
When it happens
Trigger: ValidateBlockSubtree, ValidateBlockPlacement, or ValidateBlockReplacement invoked with a nil *ast.Node — typically after looking up a block ID that is absent from the loaded tree, or passing an uninitialized node from a failed tree parse.
Common situations: Stale block IDs referenced after the document changed; transactions referencing blocks deleted in a concurrent edit; plugins caching node pointers across tree reloads.
Related errors
- not a code block
- invalid block structure: %s [%s] cannot contain %s [%s]
- invalid block structure: %s [%s] is not a content block
- heading [%s] is a leaf block and cannot have children; to pl
- data?.msg || data?.message || window.siyuan.languages._kerne
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/d1605a058b9693e4.
Report an issue: GitHub.