{"record":{"id":"b85bde66fb1aaa4d","repo":"siyuan-note/siyuan","slug":"master-password-migration-is-pending-v","errorCode":null,"errorMessage":"master password migration is pending: %v","messagePattern":"master password migration is pending: (.+?)","errorType":"http","errorClass":null,"httpStatus":200,"severity":"critical","filePath":"kernel/model/crypto.go","lineNumber":1283,"sourceCode":"\t\t\tConf.m.Unlock()\n\t\t\tConf.Save()\n\t\t\tlogging.LogInfof(\"repaired notebook crypto configuration from authenticated backup\")\n\t\t} else if !backupAuthenticated {\n\t\t\t// 同步备份可能属于另一轮完整改密；只要本地配置仍与全部笔记本一致，就继续使用本地配置，\n\t\t\t// 不覆盖候选备份，等待其余 WrappedDEK 同步完成后由新密码采用。\n\t\t\tlogging.LogWarnf(\"notebook crypto backup differs from usable local configuration; keeping both candidates\")\n\t\t}\n\t}\n\n\tif migrationPending {\n\t\t// 崩溃恢复后的首次新密码验证：确认所有笔记本都已切换到新 KEK，再生成带认证的全局备份并结束迁移。\n\t\tif !verifyKEKAgainstExistingBoxes(kek) {\n\t\t\tzeroAndClear(kek)\n\t\t\treturn nil, errMasterPasswordMigrationPending\n\t\t}\n\t\tif err = saveNotebookCryptoBackup(kek); err != nil {\n\t\t\tzeroAndClear(kek)\n\t\t\treturn nil, fmt.Errorf(\"%w: %v\", errMasterPasswordMigrationPending, err)\n\t\t}\n\t\tremoveMasterPasswordMigration()\n\t}\n\treturn kek, nil\n}\n\n// decryptBoxCrypt 用 KEK 解密 box 的 WrappedDEK。优先使用 GetBoxEncryption 的结果（conf → backup fallback），\n// 若解密失败则尝试 backup 中不同的 WrappedDEK。\n// 返回解密后的 DEK 和实际使用的 BoxCrypt（可能来自 backup）。\n// 若 backup 被使用会自动修复 conf.json 和刷新 backup。\nfunc decryptBoxCrypt(boxID string, kek []byte) (dek []byte, boxCrypt *conf.BoxEncryption, err error) {\n\tboxCrypt, err = GetBoxEncryption(boxID)\n\tif err != nil || boxCrypt == nil || len(boxCrypt.WrappedDEK) == 0 {\n\t\treturn nil, nil, fmt.Errorf(\"no encrypted key material for box [%s]\", boxID)\n\t}\n\n\tdek, err = decryptWrappedDEK(boxID, boxCrypt, kek)\n\tif err == nil {","sourceCodeStart":1265,"sourceCodeEnd":1301,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/model/crypto.go#L1265-L1301","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// in-package (kernel/model):\nfunc isMigrationPendingErr(err error) bool {\n    return errors.Is(err, errMasterPasswordMigrationPending)\n}\n\n// outside the package (sentinel is unexported):\nfunc isMigrationPendingErr(err error) bool {\n    return err != nil && strings.HasPrefix(err.Error(), \"master password migration is pending\")\n}","tryCatchPattern":"if err := verifyMasterPassword(pw); isMigrationPendingErr(err) {\n    // transient completion failure: surface restart instruction, do NOT fall back to old password flows\n    showUser(\"Password verified but recovery could not finish: \" + err.Error() + \" - free disk space and restart SiYuan, then re-enter the new password.\")\n    return\n}","preventionTips":["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"],"tags":["encryption","master-password","migration","crash-recovery","io"],"backgroundTag":"master-password-migration-pending","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}