siyuan-note/siyuan · error
parent block not found: %s
Error message
parent block not found: %s
What it means
Returned by CheckContainerParent in kernel/treenode/blocktree.go when GetBlockTree(parentID) returns nil — no blocktree row exists for the given parentID. CheckContainerParent is the guard used by insert/move block operations that locate the target by parentID (without previousID/nextID).
Source
Thrown at kernel/treenode/blocktree.go:542
// IsContainerType 按主类型缩写判断块是否为容器块(可合法接收子块)。
// 入参 abbrType 对应 BlockTree.Type(如 "d"/"h"/"p"),由 TypeAbbr 写入。
func IsContainerType(abbrType string) bool {
switch abbrType {
case "d", "b", "l", "i", "s", "callout":
return true
}
return false
}
// CheckContainerParent 校验 parentID 指向的块是否允许接收子块。
// 仅在“通过 parentID 定位插入/移动目标”(即不依赖 previousID/nextID)的场景下调用,
// 因为一旦带 previousID/nextID,事务层走的是兄弟级 InsertAfter/InsertBefore,天然合法。
// 返回 nil 表示合法;返回 error 时调用方应拒绝本次操作。
func CheckContainerParent(parentID string) error {
bt := GetBlockTree(parentID)
if nil == bt {
return fmt.Errorf("parent block not found: %s", parentID)
}
if IsContainerType(bt.Type) {
return nil
}
if "h" == bt.Type {
// 标题是叶子块,其“子内容”在数据结构上实为后续兄弟节点(由 HeadingChildren 按层级推算)。
// 把块挂成标题的 AST 子节点属于非法嵌套,应改用 previousID 定位。
return fmt.Errorf("heading [%s] is a leaf block and cannot have children; to place a block below this heading, pass previousID=<heading id> or previousID=<last block below the heading> instead of parentID", parentID)
}
return fmt.Errorf("block [%s] type %q is a leaf block and cannot have children; use previousID to place the block as its sibling instead", parentID, bt.Type)
}
// CheckListItemNesting 校验 parentID 和 childID 是否形成“列表项直含列表项”的非法嵌套。
// 嵌套列表的正确结构是 ListItem > List > ListItem,列表项不能直接作为另一个列表项的子块。
// 仅在 move 场景调用(源和目标类型均已知)。
func CheckListItemNesting(parentID, childID string) error {
parentBt := GetBlockTree(parentID)
childBt := GetBlockTree(childID)View on GitHub (pinned to 251596fc0d)
Solutions
- Verify the parentID exists with treenode.GetBlockTree(parentID) (or ExistBlockTree) before calling the insert/move API.
- If the parent is in an encrypted notebook, unlock the notebook first so its blocktree db is opened.
- Re-index the notebook (kernel index rebuild) if you suspect the blocktree row is missing for a block that does exist on disk.
Example fix
// before
err := treenode.CheckContainerParent(parentID) // -> parent block not found
// after
if treenode.GetBlockTree(parentID) == nil {
return fmt.Errorf("parent %q does not exist (deleted, stale, or locked encrypted box)", parentID)
}
err := treenode.CheckContainerParent(parentID) Defensive patterns
Strategy: validation
Validate before calling
// Resolve the parent before calling the insert/move API.
if treenode.GetBlockTree(parentID) == nil {
return fmt.Errorf("parent %q not found; deleted, stale, or locked encrypted notebook", parentID)
}
return treenode.CheckContainerParent(parentID) Prevention
- Always confirm the parent id with GetBlockTree/ExistBlockTree before parentID-based insert or move.
- Trim whitespace from IDs received from user input.
- If the parent lives in an encrypted notebook, unlock the notebook first.
When it happens
Trigger: Calling a block insert or move API with a parentID that does not correspond to any indexed block. CheckContainerParent is invoked from HTTP api/block_op.go, MCP tools/block.go, cli/cmd/block.go, and tools/asset.go whenever the operation is parentID-based.
Common situations: Stale or copied-wrong block ID; the parent block was deleted; the parent lives in an encrypted notebook that is currently locked (its blocktree db returns nil); leading/trailing whitespace in the ID.
Related errors
- block not found
- block not found: %s
- previous block not found: %s
- --id is required
- document not found: <documentID>
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/0d95e048051bf6ab.
Report an issue: GitHub.