{"record":{"id":"af89f1c2b601a209","repo":"siyuan-note/siyuan","slug":"w-v-errmasterpasswordmigrationpending","errorCode":null,"errorMessage":"%w: %v (errMasterPasswordMigrationPending)","messagePattern":"%w: (.+?) \\(errMasterPasswordMigrationPending\\)","errorType":"exception","errorClass":"errMasterPasswordMigrationPending","httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1310,"sourceCode":"\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\tkeys, keyErr := decryptHistoryKEKs(kek, nc.HistoryKEKs)\n\t\tclearHistoryKEKs(keys)\n\t\tif keyErr != nil || !verifyKEKAgainstExistingBoxes(kek, nil) || !verifyKEKAgainstEncryptedHistory(kek, &nc) {\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\tnc := currentNotebookCrypto()\n\tdek, err = decryptWrappedDEKWithHistory(boxID, boxCrypt, kek, nc)","sourceCodeStart":1292,"sourceCodeEnd":1328,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L1292-L1328","documentation":"During a pending master-password migration, deriveKEK wraps errMasterPasswordMigrationPending with the underlying failure: after the new password verified, decrypting history KEKs, verifying boxes/history, or saveNotebookCryptoBackup failed — here specifically the final authenticated global backup could not be written. The migration stays pending and the caller is told recovery must finish before the change is complete.","triggerScenarios":"First unlock with the new password after a crash/interruption during ChangeMasterPassword (migrationPending true), when saveNotebookCryptoBackup(kek) returns an error — typically a file-write failure in the Data directory (disk full, permissions, read-only volume).","commonSituations":"Disk full or read-only workspace volume; antivirus/backup software locking the backup file; permission changes on the Data directory after migration started.","solutions":["Fix the underlying write problem (free disk space, check file/dir permissions, close locking processes) and unlock again with the new password so migration completes","Ensure the workspace Data directory is writable by the SiYuan process, then retry","If migration cannot complete, restore the workspace from a pre-migration backup; note the wrapped error message reports the exact underlying cause"],"exampleFix":"// before (retrying blindly while Data dir is read-only)\nPOST /api/notebook/unlockEncryptedBox {password: newPassword}\n// after\nchmod u+w <workspace>/Data; df -h <workspace>  # then retry the same unlock with the new password","handlingStrategy":"try-catch","validationCode":"// Before resuming a pending migration, verify the workspace can write the backup file\nif err := checkWorkspaceWritable(util.DataDir); err != nil {\n    return fmt.Errorf(\"cannot complete master-password migration: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"kek, err := deriveKEK(newPassword)\nif errors.Is(err, errMasterPasswordMigrationPending) {\n    log.Printf(\"migration still pending: %v — fix the wrapped cause (disk/permissions) and unlock again\", err)\n    return\n}","preventionTips":["Monitor free disk space and Data-directory permissions before changing the master password","Avoid antivirus/backup tools locking files inside the workspace during a password change","If a migration is interrupted, fix the environment first and unlock with the new password to let it finish"],"tags":["go","kernel","encryption","migration-pending","backup-write-failed"],"backgroundTag":"file-write-failed","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}