siyuan-note/siyuan · error
can not open file, just support open folder only
Error message
can not open file, just support open folder only
What it means
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.
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.
Example fix
// before: opening a notebook whose data folder was deleted externally
await fetchPost("/api/notebook/openNotebook", { notebook: boxID });
// -> can not open file, just support open folder only
// after: guard on disk state first
if (!fs.existsSync(path.join(workspaceDir, "data", boxID))) {
await fetchPost("/api/notebook/removeNotebook", { notebook: boxID }); // clean stale entry
} else {
await fetchPost("/api/notebook/openNotebook", { notebook: boxID });
} Defensive patterns
Strategy: validation
Validate before calling
import fs from "fs";
import path from "path";
function boxDirExists(workspaceDir: string, boxID: string): boolean {
try {
return fs.statSync(path.join(workspaceDir, "data", boxID)).isDirectory();
} catch {
return false;
}
} Try / catch
try {
await fetchPost("/api/notebook/openNotebook", { notebook: boxID });
} catch (e) {
if (String(e).includes("just support open folder only")) {
// data/<boxID> missing or not a dir: restore from sync/backup or remove the stale entry
}
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- can not remove [ ] caused by it is not a dir
- Conf.Language(387)
- Create notebook [ ] folder [ ] failed
- dir [ ] has more than 1 file:\n
- Export failed
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/3902b6c6c33acf29.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/mount.go:537
}
if box := Conf.Box(boxID); nil != box {
boxConf := box.GetConf()
boxConf.Closed = true
boxConf.Sort = sort
box.SaveConf(boxConf)
}
task.AppendAsyncTaskWithDelay(task.PushMsg, 3*time.Second, util.PushErrMsg, Conf.Language(244), 7000)
go func() {
// 每次打开帮助文档时自动检查版本更新并提醒 https://github.com/siyuan-note/siyuan/issues/5057
time.Sleep(time.Second * 10)
CheckUpdate(true)
}()
}
if !gulu.File.IsDir(localPath) {
return false, errors.New("can not open file, just support open folder only")
}
for _, box := range Conf.GetOpenedBoxes() {
if box.ID == boxID {
return true, nil
}
}
// 加密笔记本必须先通过 UnlockBox 解出 DEK,否则拒绝挂载。Mount 本身不接收密码,
// 前端流程为:先调 /api/notebook/unlockBox 解锁,再调 openNotebook 挂载。
// 使用 IsEncryptedBox 统一判定(含 backup fallback,不依赖 conf 完整性)。
if IsEncryptedBox(boxID) && !IsBoxUnlocked(boxID) {
return false, errors.New("encrypted notebook locked, please unlock it first")
}
box := &Box{ID: boxID}
boxConf := box.GetConf()
boxConf.Closed = falseView on GitHub (pinned to 9f775e8a12)