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

  1. Validate the ID matches ast.IsNodeIDPattern before loading.
  2. If a legitimately deleted doc, surface 'document removed' and clean up dangling references.
  3. If the .sy file should exist, verify it is on disk under <data>/<box>/ and reindex the notebook.
  4. 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

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.