{"record":{"id":"747f91c4c1683d02","repo":"siyuan-note/siyuan","slug":"notebook-closed","errorCode":null,"errorMessage":"notebook closed","messagePattern":"notebook closed","errorType":"error_code","errorClass":"ErrBoxClosed","httpStatus":null,"severity":"error","filePath":"kernel/model/tree.go","lineNumber":203,"sourceCode":"\t\t// 从绝对路径推导 box 内相对路径作为 AAD\n\t\trelPath := filepath.ToSlash(strings.TrimPrefix(localPath, filepath.Join(util.DataDir, boxID)+string(os.PathSeparator)))\n\t\tif data, err = DecryptFile(boxID, relPath, dek, data); err != nil {\n\t\t\tlogging.LogErrorf(\"decrypt tree [path=%s] failed: %s\", localPath, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tret, err = dataparser.ParseJSONWithoutFix(data, luteEngine.ParseOptions)\n\tif err != nil {\n\t\tlogging.LogErrorf(\"parse json to tree [%s] failed: %s\", localPath, err)\n\t\treturn\n\t}\n\treturn\n}\n\nvar (\n\tErrBoxNotFound   = errors.New(\"notebook not found\")\n\tErrBoxClosed     = errors.New(\"notebook closed\")\n\tErrBlockNotFound = errors.New(\"block not found\")\n\tErrTreeNotFound  = errors.New(\"tree not found\")\n\tErrIndexing      = errors.New(\"indexing\")\n\tErrBoxUnindexed  = errors.New(\"notebook unindexed\")\n\tErrInvalidID     = errors.New(\"invalid id\")\n)\n\nfunc LoadTreeByBlockIDWithReindex(id string) (ret *parse.Tree, err error) {\n\treturn LoadTreeByBlockIDWithReindexInBox(id, \"\")\n}\n\n// LoadTreeByBlockIDWithReindexInBox 与 LoadTreeByBlockIDWithReindex 一致，但按 boxID 路由 blocktree 查询。\nfunc LoadTreeByBlockIDWithReindexInBox(id, boxID string) (ret *parse.Tree, err error) {\n\tif \"\" == id {\n\t\tlogging.LogWarnf(\"block id is empty\")\n\t\treturn nil, ErrTreeNotFound\n\t}\n","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/tree.go#L185-L221","documentation":"Sentinel error ErrBoxClosed declared at tree.go:203 ('notebook closed'). Returned when an operation requires an open notebook but the notebook is in a closed state. Throw sites include mount.go (notebook closed during mount) and import/file operations. notebooks must be opened before their trees can be read/written (encryption keys, indexing).","triggerScenarios":"Calling a write/read operation on a notebook that the user has closed (closed notebooks are listed in Conf.GetClosedBoxes()); importing into a closed notebook; opening a doc whose notebook was closed after the session started. mount.go returns it when the box is found in closed boxes rather than open ones.","commonSituations":"An encrypted notebook that the user closed (locked) — its blocktree data is in the encrypted DB and inaccessible until reopened; a notebook manually closed via the UI; an operation triggered by a plugin that does not check notebook state.","solutions":["Open the notebook first (UI notebook open action, or the corresponding API) before retrying the operation.","Check Conf.GetOpenedBoxes()/Conf.Box(id) and prompt the user to open the closed notebook.","Use errors.Is(err, model.ErrBoxClosed) to surface a 'please open notebook X' message instead of a generic error."],"exampleFix":"// before — operate on possibly-closed notebook\nerr := ImportData(boxID, ...)\n// may return ErrBoxClosed\n\n// after — ensure notebook is open\nif Conf.Box(boxID) == nil {\n    // prompt user to open notebook, then retry\n}","handlingStrategy":"validation","validationCode":"func boxIsOpen(boxID string) bool {\n    for _, b := range model.Conf.GetOpenedBoxes() {\n        if b.ID == boxID { return true }\n    }\n    return false\n}","typeGuard":null,"tryCatchPattern":"err := someBoxOp(boxID)\nif errors.Is(err, model.ErrBoxClosed) {\n    promptUserToOpenNotebook(boxID)\n}","preventionTips":["Check the notebook is in GetOpenedBoxes() before write operations.","For encrypted notebooks, ensure the user has unlocked/opened them.","Distinguish ErrBoxClosed (openable) from ErrBoxNotFound (gone)."],"tags":["notebook","box","sentinel","state","encryption"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}