siyuan-note/siyuan · error
encrypted notebook locked, please unlock it first
Error message
encrypted notebook locked, please unlock it first
What it means
Returned by mountBox (kernel/model/mount.go:462) when IsEncryptedBox(boxID) is true but IsBoxUnlocked(boxID) is false. Encrypted notebooks hold their DEK only in memory after an explicit UnlockBox call supplies the password; Mount itself takes no password, so it refuses to open a still-locked encrypted notebook.
Source
Thrown at kernel/model/mount.go:462
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 251596fc0d)
Solutions
- Call UnlockBox (model.UnlockBox / /api/notebook/unlockBox) with the master password first, then Mount.
- If the password is lost, use the backup recovery flow; otherwise the notebook cannot be opened.
- Re-unlock after every kernel restart — the unlocked state does not persist.
Example fix
// before
model.Mount(boxID) // encrypted but not unlocked -> error
// after
if err := model.UnlockBox(boxID, password); err != nil {
return err
}
_, err := model.Mount(boxID) Defensive patterns
Strategy: validation
Validate before calling
if model.IsEncryptedBox(boxID) && !model.IsBoxUnlocked(boxID) {
if err := model.UnlockBox(boxID, password); err != nil {
return err
}
}
return model.Mount(boxID) Type guard
func needsUnlock(boxID string) bool {
return model.IsEncryptedBox(boxID) && !model.IsBoxUnlocked(boxID)
} Prevention
- Always pair unlock-before-open for encrypted notebooks in client flows.
- Treat the unlocked state as session-scoped — re-establish it after restarts.
- Never cache the password; only the DEK is held in memory.
When it happens
Trigger: Calling /api/notebook/openNotebook for an encrypted notebook before calling /api/notebook/unlockBox with the correct password, or after a kernel restart that cleared the in-memory DEK.
Common situations: Kernel was restarted (DEK is volatile), the user cleared the unlock from the UI, or a script orchestrates open without the preceding unlock step.
Related errors
- encrypted notebook is locked, please unlock it first
- encrypted notebook is locked, please unlock it first
- path belongs to encrypted notebook [%s]: %s
- notebook [%s] was created but could not be opened: %w
- notebook [%s] is closed; run `notebook open --id %s` first
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/e5d14cb98fa1d847.
Report an issue: GitHub.