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

  1. Re-resolve the block ID via GetBlockTree / tree load and confirm it exists before validating.
  2. Check for concurrent modifications: reload the tree and retry the operation.
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/d1605a058b9693e4. Report an issue: GitHub.