{"record":{"id":"d1605a058b9693e4","repo":"siyuan-note/siyuan","slug":"invalid-block-structure-block-node-is-nil","errorCode":null,"errorMessage":"invalid block structure: block node is nil","messagePattern":"invalid block structure: block node is nil","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/treenode/block_structure.go","lineNumber":242,"sourceCode":"\t\titem.ListData.Num = num\n\t\titem.ListData.Delimiter = delimiter\n\t\titem.ListData.Marker = []byte(strconv.Itoa(num) + string(delimiter))\n\t\tnum++\n\t}\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":224,"sourceCodeEnd":246,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/treenode/block_structure.go#L224-L246","documentation":"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.","triggerScenarios":"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.","commonSituations":"Stale block IDs referenced after the document changed; transactions referencing blocks deleted in a concurrent edit; plugins caching node pointers across tree reloads.","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."],"exampleFix":"// before\nnode := findNode(id) // may be nil\nif err := treenode.ValidateBlockPlacement(parent, node); err != nil { ... }\n// after\nnode := findNode(id)\nif node == nil {\n    return fmt.Errorf(\"block %s not found in loaded tree\", id)\n}\nif err := treenode.ValidateBlockPlacement(parent, node); err != nil { ... }","handlingStrategy":"type-guard","validationCode":"if (node == null) { throw new Error('block ' + id + ' not found; reload the tree before validating') }","typeGuard":"function isResolvedNode(n) { return n != null && typeof n.ID === 'string' && n.ID.length > 0 }","tryCatchPattern":"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 } }","preventionTips":["Re-resolve block IDs after any concurrent edit","Never cache *ast.Node pointers across tree reloads","Null-check node lookups before passing to validators"],"tags":["ast","null-check","validation"],"backgroundTag":"null-argument","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}