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

  1. Pass a real SiYuan block/document id (the 14-digit timestamp + dash + 7-char suffix form issued by the kernel)
  2. If indexing is in progress, wait and retry; ErrIndexing signals that case
  3. 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

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)