siyuan-note/siyuan · error
cannot change master password while encrypted notebooks are…
Error message
cannot change master password while encrypted notebooks are unlocked (DEKs in memory), lock them first
What it means
ChangeMasterPassword refuses to run while one or more encrypted notebooks are unlocked, because their DEKs are in memory. Switching the KEK under cached DEKs would leave the in-memory cache inconsistent with the newly wrapped envelopes on disk, so the operation is aborted and the user must lock all encrypted notebooks first.
Solutions
- Lock every encrypted notebook (LockEncryptedBox / UI lock) so DEKs are zeroed and removed from the cache, then retry the password change
- Restart the kernel/app to guarantee a clean in-memory state if a stale DEK is suspected
- Close background jobs/plugins that may re-mount encrypted notebooks during the change
Example fix
// before
await lockEncryptedBoxes(); await changeMasterPassword(oldPw, newPw)
// after
for (const box of encryptedBoxes) { if (!isBoxLocked(box.id)) await lockBox(box.id) }
await changeMasterPassword(oldPw, newPw) Defensive patterns
Strategy: validation
Validate before calling
if (encryptedBoxes.some(b => !b.locked)) { throw new Error("lock all encrypted notebooks before changing the master password") } Try / catch
try { await changeMasterPassword(oldPw, newPw) } catch (e) { if (e.message.includes("lock them first")) { await lockAllEncryptedBoxes(); retryChange() } } Prevention
- Lock all encrypted notebooks before opening the password-change dialog
- Disable/complete background jobs that mount encrypted boxes during the change
- Restart the kernel if a stale DEK cache is suspected after a failed lock
When it happens
Trigger: Calling ChangeMasterPassword while cachedDEKs is non-empty — i.e. any encrypted notebook is in an unlocked/mounted state, or a previous lock operation failed to evict its DEK.
Common situations: User attempts a master-password change from settings while an encrypted notebook is open; a background job or plugin holds a mounted encrypted notebook; a stale DEK left by a failed unlock/lock cycle.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- cannot disable encrypted notebook feature while encrypted…
- encrypted box db not opened for box
- encrypted box db not opened for box
- 26
- Access to encrypted notebook data is not supported via this…
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/c0cab84244bd478f.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/crypto.go:1721
// Phase 2: 切换全局 verifier
// Phase 3: 写入各 box conf + backup
// Phase 4: 清除 manifest
//
// 注意:必须在所有加密笔记本都已 Unmount 的状态下调用(DEK 不在内存),否则新旧 KEK 切换会让缓存与磁盘不一致。
func ChangeMasterPassword(oldPassword, newPassword string) error {
if len(newPassword) == 0 {
return errors.New("new password must not be empty")
}
notebookCryptoMu.Lock()
defer notebookCryptoMu.Unlock()
// 改密期间不能有已 Mount 的加密笔记本(DEK 在内存),否则新旧 KEK 切换会让缓存与磁盘不一致
cachedDEKsLock.RLock()
dekCount := len(cachedDEKs)
cachedDEKsLock.RUnlock()
if dekCount > 0 {
return errors.New("cannot change master password while encrypted notebooks are unlocked (DEKs in memory), lock them first")
}
oldKEK, err := deriveKEK(oldPassword)
if err != nil {
return err
}
defer zeroAndClear(oldKEK)
nc := currentNotebookCrypto()
params, validErr := util.ValidateArgon2Params(nc.KDFParams)
if validErr != nil {
return validErr
}
newKEK := util.DeriveKey(newPassword, nc.MasterSalt, params)
defer zeroAndClear(newKEK)
newHistoryKEKs, err := rewrapHistoryKEKs(oldKEK, newKEK, nc.HistoryKEKs)
if err != nil {View on GitHub (pinned to 9f775e8a12)