{"record":{"id":"503b1f72440854cb","repo":"siyuan-note/siyuan","slug":"w-s-errmasterpasswordmigrationpending-conf-language-320","errorCode":null,"errorMessage":"%w: %s (errMasterPasswordMigrationPending; Conf.Language(320) formatted)","messagePattern":"%w: (.+?) \\(errMasterPasswordMigrationPending; Conf\\.Language\\(320\\) formatted\\)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1810,"sourceCode":"\tConf.NotebookCrypto.HistoryKEKs = newHistoryKEKs\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":1792,"sourceCodeEnd":1828,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/crypto.go#L1792-L1828","documentation":"During master-password migration Phase 3 (recovering a partially applied migration), if a box's conf.json cannot be saved after rebuilding its BoxCrypt from the per-notebook backup, the kernel returns errMasterPasswordMigrationPending with a localized detail (Conf.Language(320)) so the migration is retried on next startup. The pending-marker error signals 'not finished, recovery will run again', and the embedded string carries the box ID and underlying save error.","triggerScenarios":"recoverMasterPasswordMigration processes a migration entry; readNotebookCryptBackup succeeds, box.SaveConf(boxConf) fails while writing the rebuilt encrypted conf (disk full, permission denied, file lock held, JSON write error).","commonSituations":"Disk quota exhausted mid-migration, antivirus/file-sync tools locking conf.json, read-only workspace after a crash-restore, or interrupted previous run leaving the migration manifest in place.","solutions":["Restart SiYuan - the pending migration will be retried automatically on boot (that is what errMasterPasswordMigrationPending is for)","Free disk space and verify the workspace data directory is writable","Check for processes locking the notebook conf.json (sync clients, antivirus) and exclude the workspace from them","If SaveConf keeps failing, fix the specific cause from the embedded saveErr detail in the error text"],"exampleFix":"// before: conf.json on a full disk cannot be written\n// -> Master password change partially failed... box=...: rebuild encrypted conf from backup failed: write error\n// after: free space on the workspace volume, restart SiYuan so the pending migration recovery reruns to completion","handlingStrategy":"retry","validationCode":"// Before migration, verify conf.json of each encrypted notebook is writable\nconfPath := filepath.Join(util.WorkspaceDir, \"data\", boxID, \".siyuan\", \"conf.json\")\nif f, err := os.OpenFile(confPath, os.O_WRONLY, 0o600); err != nil {\n    return err\n} else { f.Close() }","typeGuard":null,"tryCatchPattern":"if err := model.ChangeMasterPassword(old, new); err != nil {\n    if errors.Is(err, model.ErrMasterPasswordMigrationPending) {\n        // migration partially applied; restart SiYuan - startup recovery retries automatically\n    }\n}","preventionTips":["Ensure ample free disk space before password migrations","Exclude the SiYuan workspace from antivirus and cloud-sync file locking","Do not kill the kernel mid-migration; let recovery finish after restart","Keep the workspace volume locally mounted (not a flaky network share)"],"tags":["go","encryption","file-write-failed","migration","recovery"],"backgroundTag":"file-write-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}