siyuan-note/siyuan · critical
master password migration is pending
Error message
master password migration is pending: %v
What it means
Thrown by the KEK verification path (crypto.go ~1240-1287) the first time the user successfully verifies the NEW master password after ChangeMasterPassword crashed midway and left a migration manifest. Before ending the migration it must re-verify every notebook against the new KEK and write the authenticated global notebook-crypto backup (saveNotebookCryptoBackup); if that backup write fails, errMasterPasswordMigrationPending is returned wrapped with the underlying I/O error (%v). The migration manifest is kept on purpose, so recovery is re-attempted on the next start.
Solutions
- Fix the underlying write problem (free disk space, clear file locks/AV exclusions, restore write permission to the workspace), then restart SiYuan and re-enter the new master password - recovery re-runs automatically
- Check the kernel log for the wrapped %v detail to identify which file/I/O operation failed
- If the backup file itself is corrupted, restore the workspace (conf + notebook crypt backups) from an external snapshot and retry verification
- Report to the SiYuan repo with logs if verification keeps failing with the same detail after the disk issue is resolved
Defensive patterns
Strategy: type-guard
Type guard
// in-package (kernel/model):
func isMigrationPendingErr(err error) bool {
return errors.Is(err, errMasterPasswordMigrationPending)
}
// outside the package (sentinel is unexported):
func isMigrationPendingErr(err error) bool {
return err != nil && strings.HasPrefix(err.Error(), "master password migration is pending")
} Try / catch
if err := verifyMasterPassword(pw); isMigrationPendingErr(err) {
// transient completion failure: surface restart instruction, do NOT fall back to old password flows
showUser("Password verified but recovery could not finish: " + err.Error() + " - free disk space and restart SiYuan, then re-enter the new password.")
return
} Prevention
- Verify free disk space and workspace writability before starting a master password change
- Never force-quit SiYuan while a password change is in progress; let it finish or crash naturally so the manifest recovery works
- Add antivirus/sync exclusions for the workspace conf and backup files
When it happens
Trigger: ChangeMasterPassword was interrupted between Phase 2 (verifier switch) and Phase 4 (manifest removal); on restart the user submits the new master password; verification succeeds but saveNotebookCryptoBackup fails because the workspace/config directory is not writable (disk full, permission denied, file locked by antivirus, read-only media).
Common situations: Disk exhaustion during a password change; Windows AV or sync clients (OneDrive/Dropbox) locking conf/backup files; workspace on a network share that dropped mid-write; user force-quit SiYuan during the change and the disk condition persists on restart.
Related errors
- master password migration is pending
- master password migration is pending: Master password…
- master password migration is pending: Master password…
- master password migration is pending: Master password…
- master password migration is pending: Master password…
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/b85bde66fb1aaa4d.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/crypto.go:1283
Conf.m.Unlock()
Conf.Save()
logging.LogInfof("repaired notebook crypto configuration from authenticated backup")
} else if !backupAuthenticated {
// 同步备份可能属于另一轮完整改密;只要本地配置仍与全部笔记本一致,就继续使用本地配置,
// 不覆盖候选备份,等待其余 WrappedDEK 同步完成后由新密码采用。
logging.LogWarnf("notebook crypto backup differs from usable local configuration; keeping both candidates")
}
}
if migrationPending {
// 崩溃恢复后的首次新密码验证:确认所有笔记本都已切换到新 KEK,再生成带认证的全局备份并结束迁移。
if !verifyKEKAgainstExistingBoxes(kek) {
zeroAndClear(kek)
return nil, errMasterPasswordMigrationPending
}
if err = saveNotebookCryptoBackup(kek); err != nil {
zeroAndClear(kek)
return nil, fmt.Errorf("%w: %v", errMasterPasswordMigrationPending, err)
}
removeMasterPasswordMigration()
}
return kek, nil
}
// decryptBoxCrypt 用 KEK 解密 box 的 WrappedDEK。优先使用 GetBoxEncryption 的结果(conf → backup fallback),
// 若解密失败则尝试 backup 中不同的 WrappedDEK。
// 返回解密后的 DEK 和实际使用的 BoxCrypt(可能来自 backup)。
// 若 backup 被使用会自动修复 conf.json 和刷新 backup。
func decryptBoxCrypt(boxID string, kek []byte) (dek []byte, boxCrypt *conf.BoxEncryption, err error) {
boxCrypt, err = GetBoxEncryption(boxID)
if err != nil || boxCrypt == nil || len(boxCrypt.WrappedDEK) == 0 {
return nil, nil, fmt.Errorf("no encrypted key material for box [%s]", boxID)
}
dek, err = decryptWrappedDEK(boxID, boxCrypt, kek)
if err == nil {View on GitHub (pinned to afa823b6b4)