siyuan-note/siyuan · error
encrypted notebook locked, please unlock it first
Error message
encrypted notebook locked, please unlock it first
What it means
Encrypted notebooks store their data encrypted under a DEK wrapped by the user's password. Mount never receives a password, so it requires the box to already be unlocked via UnlockBox (/api/notebook/unlockBox); IsEncryptedBox (including backup fallback) is true while IsBoxUnlocked is false, and mounting is refused. The intended frontend flow is: unlockBox first, then openNotebook.
Solutions
- Call /api/notebook/unlockBox with the notebook password first, then call openNotebook.
- Check lock state beforehand (IsEncryptedBox/IsBoxUnlocked equivalents via API) and route to the unlock UI when locked.
- After a kernel restart, expect all encrypted notebooks to be locked again and re-run the unlock flow.
- If the password is forgotten, use the documented recovery flow rather than bypassing the lock.
Example fix
// before: mounting an encrypted notebook directly
await fetchPost("/api/notebook/openNotebook", { notebook: boxID });
// -> encrypted notebook locked, please unlock it first
// after: unlock before mount
await fetchPost("/api/notebook/unlockBox", { notebook: boxID, password });
await fetchPost("/api/notebook/openNotebook", { notebook: boxID }); Defensive patterns
Strategy: try-catch
Validate before calling
// query state before mounting
const boxes = (await fetchPost("/api/notebook/lsNotebooks", {})).data.notebooks;
// if the notebook is encrypted and not yet unlocked, unlockBox must run first
async function ensureOpen(boxID: string, password: string): Promise<void> {
await fetchPost("/api/notebook/unlockBox", { notebook: boxID, password });
await fetchPost("/api/notebook/openNotebook", { notebook: boxID });
} Try / catch
try {
await fetchPost("/api/notebook/openNotebook", { notebook: boxID });
} catch (e) {
if (String(e).includes("encrypted notebook locked")) {
await promptUnlockDialog(boxID); // collects password, calls unlockBox, then openNotebook
}
} Prevention
- Treat every encrypted notebook as locked after each kernel restart
- Always follow the unlockBox -> openNotebook sequence in plugins and scripts
- Surface the unlock dialog instead of failing silently when openNotebook returns this error
When it happens
Trigger: Calling /api/notebook/openNotebook (Mount) on an encrypted notebook after kernel restart or before ever entering the password; calling Mount concurrently with a flow that re-locked the box; a plugin opening the notebook directly instead of going through the unlock dialog.
Common situations: Automated scripts or plugins that call openNotebook without knowing the notebook is encrypted; session loss after kernel reboot (DEK is memory-only); attempting to mount an encrypted notebook whose unlock was cancelled by the user.
Related errors
- cannot replay block swap across encrypted notebook…
- cannot swap blocks across encrypted notebook boundaries
- CLI does not support encrypted notebook
- Conf.Language(311)
- Conf.Language(314)
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/e5d14cb98fa1d847.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/mount.go:550
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
if err := box.SaveConf(boxConf); err != nil {
logging.LogErrorf("save box conf [%s] failed: %s", boxID, err)
}
if boxConf.Encrypted {
markRuntimeEncryptedBox(boxID)
mountedEncryptedBoxes.Store(boxID, true)
}
if _, ensureErr := EnsureBoxDoc(boxID); nil != ensureErr {
logging.LogErrorf("ensure box document [%s] failed: %s", boxID, ensureErr)
}
// 缓存根一级的文档树展开
files, _, _ := ListDocTree(box.ID, "/", util.SortModeUnassigned, false, false, Conf.FileTree.MaxListCount)View on GitHub (pinned to 9f775e8a12)