{"record":{"id":"3902b6c6c33acf29","repo":"siyuan-note/siyuan","slug":"can-not-open-file-just-support-open-folder-only","errorCode":null,"errorMessage":"can not open file, just support open folder only","messagePattern":"can not open file, just support open folder only","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/mount.go","lineNumber":537,"sourceCode":"\t\t}\n\n\t\tif box := Conf.Box(boxID); nil != box {\n\t\t\tboxConf := box.GetConf()\n\t\t\tboxConf.Closed = true\n\t\t\tboxConf.Sort = sort\n\t\t\tbox.SaveConf(boxConf)\n\t\t}\n\n\t\ttask.AppendAsyncTaskWithDelay(task.PushMsg, 3*time.Second, util.PushErrMsg, Conf.Language(244), 7000)\n\t\tgo func() {\n\t\t\t// 每次打开帮助文档时自动检查版本更新并提醒 https://github.com/siyuan-note/siyuan/issues/5057\n\t\t\ttime.Sleep(time.Second * 10)\n\t\t\tCheckUpdate(true)\n\t\t}()\n\t}\n\n\tif !gulu.File.IsDir(localPath) {\n\t\treturn false, errors.New(\"can not open file, just support open folder only\")\n\t}\n\n\tfor _, box := range Conf.GetOpenedBoxes() {\n\t\tif box.ID == boxID {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\t// 加密笔记本必须先通过 UnlockBox 解出 DEK，否则拒绝挂载。Mount 本身不接收密码，\n\t// 前端流程为：先调 /api/notebook/unlockBox 解锁，再调 openNotebook 挂载。\n\t// 使用 IsEncryptedBox 统一判定（含 backup fallback，不依赖 conf 完整性）。\n\tif IsEncryptedBox(boxID) && !IsBoxUnlocked(boxID) {\n\t\treturn false, errors.New(\"encrypted notebook locked, please unlock it first\")\n\t}\n\n\tbox := &Box{ID: boxID}\n\tboxConf := box.GetConf()\n\tboxConf.Closed = false","sourceCodeStart":519,"sourceCodeEnd":555,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/mount.go#L519-L555","documentation":"mountBox resolves boxID to <workspace>/data/<boxID> and requires it to be a directory; gulu.File.IsDir fails for files, broken symlinks, or missing paths, so opening the notebook is refused with \"just support open folder only\". Unlike RemoveBox, Mount checks the path later, after lock acquisition and flush, so the ID format is valid but the filesystem entry is not a folder.","triggerScenarios":"Calling Mount/openNotebook for an ID whose data/<boxID> is a regular file, a broken symlink, or does not exist at all (deleted externally while conf still lists it, or a data/ entry that is not a notebook directory).","commonSituations":"Notebook folder deleted or moved by hand/sync while the notebook is still closed in conf; a file dropped into data/ with an ID-like name; restoring a workspace partially so the directory is missing; case-sensitivity mismatches on case-sensitive filesystems.","solutions":["Verify <workspace>/data/<boxID> exists and is a directory; restore or recreate it (re-sync, restore from backup, or re-create the notebook).","Remove the stale notebook entry via removeNotebook (or closeNotebook) if the data is intentionally gone.","If it is a stray file at that path, move it away and recreate the notebook directory.","Re-run index/rebuild or reopen the workspace so conf and disk are back in sync."],"exampleFix":"// before: opening a notebook whose data folder was deleted externally\nawait fetchPost(\"/api/notebook/openNotebook\", { notebook: boxID });\n// -> can not open file, just support open folder only\n\n// after: guard on disk state first\nif (!fs.existsSync(path.join(workspaceDir, \"data\", boxID))) {\n  await fetchPost(\"/api/notebook/removeNotebook\", { notebook: boxID }); // clean stale entry\n} else {\n  await fetchPost(\"/api/notebook/openNotebook\", { notebook: boxID });\n}","handlingStrategy":"validation","validationCode":"import fs from \"fs\";\nimport path from \"path\";\n\nfunction boxDirExists(workspaceDir: string, boxID: string): boolean {\n  try {\n    return fs.statSync(path.join(workspaceDir, \"data\", boxID)).isDirectory();\n  } catch {\n    return false;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await fetchPost(\"/api/notebook/openNotebook\", { notebook: boxID });\n} catch (e) {\n  if (String(e).includes(\"just support open folder only\")) {\n    // data/<boxID> missing or not a dir: restore from sync/backup or remove the stale entry\n  }\n}","preventionTips":["Do not delete or move notebook folders while the kernel is running except via the app","Verify data/<boxID> exists before calling openNotebook in scripts","Keep sync/backup restores complete so conf and disk stay consistent"],"tags":["filesystem","notebook","workspace","missing-directory"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}