siyuan-note/siyuan · error · ErrBoxNotFound
notebook not found
Error message
notebook not found
What it means
Sentinel error ErrBoxNotFound declared at tree.go:202 ('notebook not found'). Returned by tree/block/transaction operations when the referenced notebook (box) ID does not correspond to any known notebook in the configuration. Callers compare with errors.Is(err, model.ErrBoxNotFound). Throw sites include box_doc.go, block.go, mount.go (Conf.Box returns nil), transaction.go, and the AV new-item path.
Source
Thrown at kernel/model/tree.go:202
}
// 从绝对路径推导 box 内相对路径作为 AAD
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
}View on GitHub (pinned to 251596fc0d)
Solutions
- Verify the boxID exists via Conf.GetBoxes()/Conf.Box(id) before issuing the operation.
- Refresh the notebook list in the UI/API client to drop stale boxIDs.
- If the notebook was deleted intentionally, propagate the 'not found' state to the user rather than retrying.
- Use errors.Is(err, model.ErrBoxNotFound) to distinguish from ErrBoxClosed.
Example fix
// before — assumes box exists
box := Conf.Box(boxID)
tree, err := loadTreeByBlockTree(box) // nil deref or ErrBoxNotFound later
// after — guard first
box := Conf.Box(boxID)
if box == nil {
return ErrBoxNotFound
} Defensive patterns
Strategy: validation
Validate before calling
func boxExists(boxID string) bool {
return model.Conf.Box(boxID) != nil
} Try / catch
tree, err := model.LoadTreeByBlockIDInBox(id, boxID)
if errors.Is(err, model.ErrBoxNotFound) {
return fmt.Errorf("notebook %s does not exist", boxID)
} Prevention
- Validate boxID via Conf.Box(id) before any notebook-scoped operation.
- Refresh cached notebook lists after create/delete operations.
- Always use errors.Is for sentinel comparison, never string matching.
When it happens
Trigger: An operation targets a boxID that is not registered in conf.json notebooks; the notebook was deleted but a stale reference remains; an API call (filetree, AV) passes a boxID that never existed or was removed. mount.go returns it when Conf.Box(id) is nil.
Common situations: A plugin or external API client caches an old boxID after the user deletes the notebook; a sync removed/recreated notebooks leaving dangling references; a typo in the boxID; cross-notebook operation where the target notebook was removed mid-operation.
Related errors
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/d446a4e189ad465d.
Report an issue: GitHub.