siyuan-note/siyuan · error · ErrTreeNotFound
tree not found
Error message
tree not found
What it means
Sentinel error ErrTreeNotFound declared at tree.go:205 ('tree not found'). Returned when a document/tree cannot be located — distinct from ErrBlockNotFound in that it concerns the document (root tree) rather than an individual block. Throw sites include tree.go (LoadTreeByBlockID variants when blocktree entry is absent even after filesystem reindex attempt), history_diff.go, block.go:561, and indexTreeInFilesystem when the tree is absent on disk.
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 251596fc0d)
Solutions
- Validate the ID matches ast.IsNodeIDPattern before loading.
- If a legitimately deleted doc, surface 'document removed' and clean up dangling references.
- If the .sy file should exist, verify it is on disk under <data>/<box>/ and reindex the notebook.
- Use errors.Is(err, model.ErrTreeNotFound) to distinguish from ErrIndexing (retry) vs genuine not-found.
Example fix
// before — pass unvalidated id
tree, err := LoadTreeByBlockID(maybeBadID)
// after — validate pattern first
if !ast.IsNodeIDPattern(id) {
return nil, ErrTreeNotFound
} Defensive patterns
Strategy: type-guard
Validate before calling
import "github.com/siyuan-note/siyuan/kernel/treenode/.../ast"
func isValidDocID(id string) bool {
return ast.IsNodeIDPattern(id)
} Type guard
func isLoadableDocID(id string) bool {
if !ast.IsNodeIDPattern(id) { return false }
return treenode.GetBlockTree(id) != nil
} Try / catch
tree, err := model.LoadTreeByBlockID(id)
if errors.Is(err, model.ErrTreeNotFound) {
if !ast.IsNodeIDPattern(id) {
return fmt.Errorf("invalid doc id format")
}
return fmt.Errorf("document not found")
} Prevention
- Validate ID format with ast.IsNodeIDPattern before loading.
- Clean up bookmarks/refs to deleted docs.
- Reindex the notebook if a doc that should exist returns ErrTreeNotFound.
When it happens
Trigger: Loading a document whose root block ID has no blocktree entry and is not found on the filesystem either (indexTreeInFilesystem returns it after findUnindexedTreePathInAllBoxes yields nothing); an invalid/non-node-ID-pattern id passed to loadTreeByBlockIDInBox0 (ast.IsNodeIDPattern fails); a document was deleted from disk but referenced.
Common situations: Document deleted but a bookmark/history/external link still references it; an ID that is not a valid 20-char node ID pattern; the .sy file was removed manually from the data dir; cross-workspace doc reference where the doc does not exist.
Related errors
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/275c49555050728d.
Report an issue: GitHub.