{"record":{"id":"3d3f2ef3ad601f5b","repo":"siyuan-note/siyuan","slug":"master-password-change-partially-failed-please-re","errorCode":null,"errorMessage":"Master password change partially failed. Please restart SiYuan to complete recovery. Detail: %s","messagePattern":"Master password change partially failed\\. Please restart SiYuan to complete recovery\\. Detail: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1780,"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":1762,"sourceCodeEnd":1798,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L1762-L1798","documentation":"Thrown in ChangeMasterPassword Phase 3 when a notebook's conf is missing/damaged and the attempt to rebuild it from the per-notebook backup fails at box.SaveConf. This wraps errMasterPasswordMigrationPending (a sentinel) with Language(320), producing the user-facing 'Master password change partially failed. Please restart SiYuan to complete recovery.' The migration manifest has already been written (Phase 1) and the global verifier switched (Phase 2), so the state is recoverable.","triggerScenarios":"During Phase 3 of ChangeMasterPassword, for a notebook whose boxConf.Encrypted is false or boxConf.BoxCrypt is nil: readNotebookCryptBackup succeeds (backup has WrappedDEK), but box.SaveConf fails (filesystem write error). The migration is left partially applied — global verifier is new, but this notebook's conf hasn't been updated.","commonSituations":"Disk full or filesystem write error during password change. Permissions changed between read and write. File locking conflict with another process. The manifest on disk ensures that on next restart, recoverMasterPasswordMigration will detect the pending state and retry the conf writes.","solutions":["Restart SiYuan — recoverMasterPasswordMigration runs during Boot and will complete the pending conf writes using the migration manifest.","Check the detail string in the error message for the specific SaveConf failure (e.g., 'permission denied', 'no space left on device').","Free disk space or fix permissions, then restart so recovery can succeed.","Do not manually edit conf.json or the manifest while a migration is pending — let the recovery logic handle it."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// These errors (633-637) all wrap errMasterPasswordMigrationPending.\n// The correct handling is to detect the sentinel and inform the user\n// to restart SiYuan for recovery:\nif errors.Is(err, model.ErrMasterPasswordMigrationPending) {\n    // inform user: restart SiYuan to complete recovery\n    // recovery runs automatically during Boot\n}","preventionTips":["Always restart SiYuan after a partial password-change failure — recovery is automatic.","Ensure sufficient disk space before changing passwords.","Do not manually edit conf.json or the migration manifest while a migration is pending."],"tags":["encryption","password-change","migration-recovery","go"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}