{"record":{"id":"b998b7c62906c970","repo":"siyuan-note/siyuan","slug":"master-password-migration-is-pending-master-passw","errorCode":null,"errorMessage":"master password migration is pending: Master password change partially failed. Please restart SiYuan to complete recovery. Detail: %s: rebuild encrypted conf from backup failed: %s","messagePattern":"master password migration is pending: Master password change partially failed\\. Please restart SiYuan to complete recovery\\. Detail: (.+?): rebuild encrypted conf from backup failed: (.+?)","errorType":"exception","errorClass":"errMasterPasswordMigrationPending","httpStatus":null,"severity":"critical","filePath":"kernel/model/crypto.go","lineNumber":1783,"sourceCode":"\tConf.NotebookCrypto.KDFParams = params\n\tConf.m.Unlock()\n\n\t// Conf.Save 内部会加 Conf.m，不能在持锁状态下调用（RWMutex 不可重入）\n\tConf.Save()\n\n\t// Phase 3: 写入各 box conf + backup\n\tfor _, entry := range entries {\n\t\tbox := &Box{ID: entry.BoxID}\n\t\tboxConf := box.GetConf()\n\t\tif !boxConf.Encrypted || boxConf.BoxCrypt == nil {\n\t\t\t// conf 缺失/损坏：尝试从 per-notebook backup 重建\n\t\t\tbackup, bErr := readNotebookCryptBackup(entry.BoxID)\n\t\t\tif bErr == nil && backup != nil && len(backup.WrappedDEK) > 0 {\n\t\t\t\tboxConf = box.GetConf()\n\t\t\t\tboxConf.Encrypted = true\n\t\t\t\tboxConf.BoxCrypt = backup\n\t\t\t\tif saveErr := box.SaveConf(boxConf); saveErr != nil {\n\t\t\t\t\treturn fmt.Errorf(\"%w: %s\", errMasterPasswordMigrationPending,\n\t\t\t\t\t\tfmt.Sprintf(Conf.Language(320), entry.BoxID+\": rebuild encrypted conf from backup failed: \"+saveErr.Error()))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// conf 与 backup 均不可用：manifest 是该 box 加密密钥的权威来源，直接从 entry 重建 BoxCrypt，\n\t\t\t\t// 避免改密因瞬时 conf 损坏而中断（详见 recoverMasterPasswordMigration 中的对称处理）。\n\t\t\t\tlogging.LogWarnf(\"rebuild encrypted box [%s] from migration entry (conf and backup both unavailable)\", entry.BoxID)\n\t\t\t\tboxConf = box.GetConf()\n\t\t\t\tboxConf.Encrypted = true\n\t\t\t\tboxConf.BoxCrypt = &conf.BoxEncryption{\n\t\t\t\t\tWrappedDEK: entry.NewWrappedDEK,\n\t\t\t\t\tWrapNonce:  entry.NewWrapNonce,\n\t\t\t\t\tSpec:       entry.NewSpec,\n\t\t\t\t\tMetadata:   entry.Metadata,\n\t\t\t\t\tCreatedAt:  time.Now().UnixMilli(),\n\t\t\t\t}\n\t\t\t\tif saveErr := box.SaveConf(boxConf); saveErr != nil {\n\t\t\t\t\treturn fmt.Errorf(\"%w: %s\", errMasterPasswordMigrationPending,\n\t\t\t\t\t\tfmt.Sprintf(Conf.Language(320), entry.BoxID+\": rebuild encrypted conf from migration entry failed: \"+saveErr.Error()))","sourceCodeStart":1765,"sourceCodeEnd":1801,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/model/crypto.go#L1765-L1801","documentation":"Returned by ChangeMasterPassword Phase 3 (crypto.go:1777-1785) when a notebook's conf.json was missing/corrupt, the per-notebook backup was successfully read as the rebuild source, but writing the rebuilt conf back to disk (box.SaveConf) failed. It wraps errMasterPasswordMigrationPending with localized message 320, telling the user to restart; the migration manifest written in Phase 1 remains the authoritative recovery record.","triggerScenarios":"Disk full, permission denied, or a file lock on <data>/<boxID>/.siyuan/conf.json exactly while the change-password loop rewrites notebook configs; antivirus/indexer briefly locking the file on Windows.","commonSituations":"Insufficient disk space during a many-notebook migration; workspace guarded by sync/AV software; conf.json made read-only by backup tools.","solutions":["Free disk space / clear the file lock / fix permissions on the notebook's .siyuan/conf.json","Restart SiYuan - recoverMasterPasswordMigration replays the manifest and completes the switch; then verify with the NEW password","Confirm via kernel logs that recovery finished before unlocking notebooks or changing the password again"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isMigrationPendingErr(err error) bool {\n    return errors.Is(err, errMasterPasswordMigrationPending) // in-package; outside use string prefix check\n}","tryCatchPattern":"if err := model.ChangeMasterPassword(oldPw, newPw); isMigrationPendingErr(err) {\n    // change half-done BY DESIGN: manifest is on disk, restart + new password completes it\n    showUser(Conf.Language(320)) // 'restart SiYuan to complete recovery'\n    return\n}","preventionTips":["Ensure free disk space and writable conf.json for every encrypted notebook before changing the password","Never force-quit during a change; the manifest recovery depends on a clean restart","After any restart following this error, verify with the new password before doing anything else"],"tags":["encryption","master-password","migration","io","conf"],"backgroundTag":"master-password-migration-pending","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}