{"record":{"id":"97489e6ef8ab7cae","repo":"siyuan-note/siyuan","slug":"master-password-migration-is-pending-master-passw-97489e","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 migration entry failed: %s","messagePattern":"master password migration is pending: Master password change partially failed\\. Please restart SiYuan to complete recovery\\. Detail: (.+?): rebuild encrypted conf from migration entry failed: (.+?)","errorType":"exception","errorClass":"errMasterPasswordMigrationPending","httpStatus":null,"severity":"critical","filePath":"kernel/model/crypto.go","lineNumber":1800,"sourceCode":"\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()))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tboxConf.BoxCrypt.WrappedDEK = entry.NewWrappedDEK\n\t\tboxConf.BoxCrypt.Spec = entry.NewSpec\n\t\tboxConf.BoxCrypt.WrapNonce = entry.NewWrapNonce\n\t\tboxConf.BoxCrypt.Metadata = append([]byte(nil), entry.Metadata...)\n\t\tif err = box.SaveConf(boxConf); err != nil {\n\t\t\treturn fmt.Errorf(\"%w: %s\", errMasterPasswordMigrationPending,\n\t\t\t\tfmt.Sprintf(Conf.Language(320), entry.BoxID+\": save conf failed: \"+err.Error()))\n\t\t}\n\t\tif err = writeNotebookCryptBackup(entry.BoxID, boxConf.BoxCrypt); err != nil {\n\t\t\treturn fmt.Errorf(\"%w: %s\", errMasterPasswordMigrationPending,\n\t\t\t\tfmt.Sprintf(Conf.Language(320), entry.BoxID+\": update notebook crypt backup failed: \"+err.Error()))\n\t\t}\n\t}\n","sourceCodeStart":1782,"sourceCodeEnd":1818,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/model/crypto.go#L1782-L1818","documentation":"Returned by ChangeMasterPassword Phase 3 (crypto.go:1786-1802) when both the notebook conf.json and the per-notebook backup are unavailable, so the code rebuilds BoxCrypt directly from the migration manifest entry (the authoritative source of the new key material) - and that rebuild's box.SaveConf still fails. The migration manifest is preserved; restart completes recovery.","triggerScenarios":"A notebook whose conf.json is corrupt/unwritable and whose backup is missing/damaged, combined with a write failure (disk full, permissions, file lock) when saving the reconstructed conf.json during the password change.","commonSituations":"Notebooks already in a degraded state (deleted backup file plus damaged conf) hit during password rotation; failing disk; workspace permission changes made between sessions.","solutions":["Repair write access to <data>/<boxID>/.siyuan/ (space, permissions, locks) for the box named in the detail","Restart SiYuan so migration recovery replays the manifest and retries the rebuild, then log in with the new password","After recovery, verify the affected notebook unlocks and re-exports/backups its key material (a fresh crypt backup is written on next successful unlock)"],"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    logMigrationDetail(err) // names the box whose conf+backup rebuild failed\n    instructUserRestart()   // recovery replays the manifest entry for that box\n    return\n}","preventionTips":["Keep per-notebook crypt backups intact - they are the first rebuild source when conf is damaged","Repair notebooks with missing/corrupt conf AND backup before rotating the master password","Maintain writability of <data>/<boxID>/.siyuan/ for every encrypted notebook during the change"],"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"}