{"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":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1282,"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":1264,"sourceCodeEnd":1300,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L1264-L1300","documentation":"Thrown by deriveKEK (crypto.go:1282) during the finalization of a master-password migration: the new KEK was verified against all existing boxes (verifyKEKAgainstExistingBoxes true), migrationPending is true, but saveNotebookCryptoBackup(kek) failed while writing the new authenticated global backup. It wraps errMasterPasswordMigrationPending with the underlying save error, so the migration stays pending and will be retried on the next correct-password authentication.","triggerScenarios":"deriveKEK reaches the migrationPending branch after verifier+box verification pass, then saveNotebookCryptoBackup returns a non-nil I/O error writing dataCryptoBackupPath(). The migration file is not removed, so the next deriveKEK with the new password re-attempts finalization.","commonSituations":"Disk full, permission denied, or a locked backup file exactly when finalizing a password change. Read-only mount during migration. Antivirus locking the file on Windows.","solutions":["Fix the underlying I/O error on the backup path (free space, permissions, locks), then re-authenticate with the new master password to let migration finalization retry.","Do NOT manually delete the migration marker; let deriveKEK re-run saveNotebookCryptoBackup once the filesystem is healthy.","Inspect the wrapped error tail to identify the exact syscall/path failure."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-flight: ensure backup path is writable so migration can finalize.\nfunc backupWritable() error {\n    p := dataCryptoBackupPath()\n    if dir := filepath.Dir(p); dir != \"\" {\n        f, err := os.CreateTemp(dir, \"probe-*\")\n        if err != nil {\n            return fmt.Errorf(\"backup dir not writable: %w\", err)\n        }\n        f.Close()\n        os.Remove(f.Name())\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := model.UnlockBox(boxID, password, boxCrypt); err != nil {\n    if errors.Is(err, model.ErrMasterPasswordMigrationPending) || strings.Contains(err.Error(), \"migration is pending\") {\n        // fix the I/O cause, then re-authenticate with the new password to finalize migration\n        respond(c, \"migration still pending (\"+err.Error()+\"); fix storage and re-enter new password\")\n        return\n    }\n    respond(c, err.Error())\n}","preventionTips":["Free space / fix permissions before finalizing a password change.","Do not delete the migration marker manually; let deriveKEK retry finalization.","Re-authenticate with the new password once storage is healthy."],"tags":["encryption","notebook-crypto","key-rotation","migration","io"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}