siyuan-note/siyuan · error
tree not found
Error message
tree not found
What it means
Sentinel error model.ErrTreeNotFound (kernel/model/tree.go:205). Returned when the document tree for an ID cannot be located or loaded: an empty id (loadTreeByBlockIDWithReindexInBox logs 'block id is empty' and returns it), an ID failing ast.IsNodeIDPattern, or no blocktree entry even after the kernel's fallback attempt to index the tree from the filesystem (indexTreeInFilesystem).
Solutions
- Pass a real SiYuan block/document id (the 14-digit timestamp + dash + 7-char suffix form issued by the kernel)
- If indexing is in progress, wait and retry; ErrIndexing signals that case
- If the id should exist, rebuild the index (设置 - 搜索 - 重建索引) to re-derive blocktree from the .sy files on disk
Defensive patterns
Strategy: try-catch
Validate before calling
// Validate shape and existence up front:
if !ast.IsNodeIDPattern(id) || id == "" {
// reject before calling the kernel
}
// POST /api/query/block {"id": id} to confirm it resolves Type guard
func isTreeNotFound(err error) bool {
return errors.Is(err, model.ErrTreeNotFound)
} Try / catch
if tree, err := model.LoadTreeByBlockID(id); err != nil {
if errors.Is(err, model.ErrTreeNotFound) {
// empty/malformed id, or doc truly gone - do not blindly retry
} else if errors.Is(err, model.ErrIndexing) {
// retry after indexing completes
}
} Prevention
- Always propagate kernel-issued ids end-to-end; never synthesize them
- Check for empty id before calling load APIs
- Keep ErrIndexing and ErrTreeNotFound handling distinct: one is transient, the other is not
When it happens
Trigger: Passing an empty id string; passing a malformed id that does not match the SiYuan node-ID pattern; an id whose .sy file or blocktree entry is gone (manually deleted files, corrupted blocktree.db) so even the reindex fallback cannot find it. Distinguish from ErrIndexing: if a kernel-wide index task is running, that sentinel is returned instead.
Common situations: Callers forwarding unvalidated query params; ids truncated in copy-paste; hand-edited or externally-deleted notebook contents desyncing blocktree.db from disk.
Related errors
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/275c49555050728d.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/tree.go:205
if data, err = DecryptFile(boxID, relPath, dek, data); err != nil {
logging.LogErrorf("decrypt tree [path=%s] failed: %s", localPath, err)
return
}
}
ret, err = dataparser.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
if err != nil {
logging.LogErrorf("parse json to tree [%s] failed: %s", localPath, err)
return
}
return
}
var (
ErrBoxNotFound = errors.New("notebook not found")
ErrBoxClosed = errors.New("notebook closed")
ErrBlockNotFound = errors.New("block not found")
ErrTreeNotFound = errors.New("tree not found")
ErrIndexing = errors.New("indexing")
ErrBoxUnindexed = errors.New("notebook unindexed")
ErrInvalidID = errors.New("invalid id")
)
func LoadTreeByBlockIDWithReindex(id string) (ret *parse.Tree, err error) {
return LoadTreeByBlockIDWithReindexInBox(id, "")
}
// LoadTreeByBlockIDWithReindexInBox 与 LoadTreeByBlockIDWithReindex 一致,但按 boxID 路由 blocktree 查询。
func LoadTreeByBlockIDWithReindexInBox(id, boxID string) (ret *parse.Tree, err error) {
if "" == id {
logging.LogWarnf("block id is empty")
return nil, ErrTreeNotFound
}
bt := treenode.GetBlockTreeInBox(id, boxID)
if nil == bt && "" == boxID {View on GitHub (pinned to afa823b6b4)