siyuan-note/siyuan · error

ErrTreeNotFound

ErrTreeNotFound

Error message

tree not found

What it means

ErrTreeNotFound is the sentinel returned when the document tree (.sy) for a block/doc ID cannot be loaded — the document file is missing from the notebook. API handlers special-case it to show localized 'doc not found' messages (Conf.Language(15)) instead of dumping the raw error.

Solutions

  1. Confirm the doc ID exists (query blocks table or list files under data/<box>/) and fix stale references
  2. Sync the workspace so missing docs are restored or removed references converge
  3. Map errors.Is(err, model.ErrTreeNotFound) to the localized message (Conf.Language(15)) in handlers rather than generic error text
  4. Reindex the notebook if data files exist but the index is stale (though typically the file is genuinely gone)

Example fix

// before
ret.Msg = err.Error()

// after
case errors.Is(err, model.ErrTreeNotFound):
    ret.Msg = fmt.Sprintf(model.Conf.Language(15), id)
default:
    ret.Msg = err.Error()
Defensive patterns

Strategy: fallback

Validate before calling

// TypeScript: check the doc path resolves before loading by ID
const p = await fetchPost('/api/filetree/getHPathByID', { id });
if (p.code !== 0) throw new Error('doc missing: ' + id);

Type guard

function isTreeNotFound(msg: string | null | undefined): boolean {
  return msg === 'tree not found';
}

Try / catch

// Go
case errors.Is(err, model.ErrTreeNotFound):
    ret.Msg = fmt.Sprintf(model.Conf.Language(15), id)
default:
    ret.Msg = err.Error()

Prevention

When it happens

Trigger: getDoc/getBlockInfo paths (kernel/api/block.go:762) load a tree by block or doc ID and the .sy file does not exist; contract getters (getHPathByIDContract, getPathByIDContract, getFullHPathByIDContract) and export preflight (prepareExportAssets) propagate it; LoadTreeByBlockIDWithReindex fails to find the doc.

Common situations: Doc deleted on another device and sync removed the .sy while an old link/ID is still requested; stale browser tab holding a removed doc ID; corrupted or manually deleted data/ folder contents; export of a doc that no longer exists.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/4562081f91cefdb5. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/tree.go:213

		}
	}

	if err = treenode.CheckSpecJSON(data); nil != 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 9f775e8a12)