siyuan-note/siyuan · error
block not found
Error message
block not found
What it means
Sentinel error model.ErrBlockNotFound (kernel/model/tree.go:204), the most common not-found error in the kernel. It means a block ID has no entry in the blocktree (treenode.GetBlockTree returned nil) or a document path failed validation. Dozens of call sites return it (block.go, heading.go, listitem.go, search.go, export.go), and the transaction layer maps it to TxErrCodeBlockNotFound.
Solutions
- Verify the id exists first, e.g. POST /api/query/block {"id": "<blockID>"} or /api/block/getBlockInfo
- If indexing may be in flight, wait for it to finish and retry once
- If the id is persistently missing, rebuild the index (设置 - 搜索 - 重建索引) or verify the block was not deleted by the user
- Match with errors.Is(err, model.ErrBlockNotFound); in transactions treat TxErr code TxErrCodeBlockNotFound as 'refresh your id cache'
Defensive patterns
Strategy: try-catch
Validate before calling
// Confirm the block exists before operating on it:
// POST /api/query/block {"id": "<blockID>"}
// Empty result -> do not call block-mutating APIs; refresh your id source. Type guard
func isBlockNotFound(err error) bool {
return errors.Is(err, model.ErrBlockNotFound)
} Try / catch
if err := model.SomeBlockOp(id); err != nil {
switch {
case errors.Is(err, model.ErrIndexing):
// wait for indexing, retry once
case errors.Is(err, model.ErrBlockNotFound):
// id is stale or deleted - drop it from caches, do not retry
default:
// propagate
}
} Prevention
- Validate block IDs with a blocks query before mutating operations
- In transactions, handle TxErr code TxErrCodeBlockNotFound by refreshing cached ids
- After user deletions or syncs, treat cached block IDs as suspect
When it happens
Trigger: Operations on a stale or deleted block ID (user removed the block after your code captured the id); calling block APIs while the database is still indexing; invalid document paths in file ops (tests in file_test.go show malformed parent paths also map to this sentinel).
Common situations: Plugins caching block IDs in long-lived state; API retries racing user deletions; fresh kernel boot before indexing commits; referencing blocks of an unindexed notebook.
Related errors
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/07e8c4938ec04ccd.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/tree.go:204
relPath := filepath.ToSlash(strings.TrimPrefix(localPath, filepath.Join(util.DataDir, boxID)+string(os.PathSeparator)))
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)View on GitHub (pinned to afa823b6b4)