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

  1. Verify <workspace>/data/<boxID> exists and is a directory; restore or recreate it (re-sync, restore from backup, or re-create the notebook).
  2. Remove the stale notebook entry via removeNotebook (or closeNotebook) if the data is intentionally gone.
  3. If it is a stray file at that path, move it away and recreate the notebook directory.
  4. 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

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


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 = false

View on GitHub (pinned to 9f775e8a12)