{"record":{"id":"fbc1a06019402733","repo":"siyuan-note/siyuan","slug":"failed-to-persist-key-backup-w","errorCode":null,"errorMessage":"failed to persist key backup: %w","messagePattern":"failed to persist key backup: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":365,"sourceCode":"\tif !verifyKEKMAC(nc, kek) {\n\t\treturn errors.New(Conf.Language(317))\n\t}\n\tdecrypted, dErr := util.DecryptWithAAD(kek, nc.KEKVerifier, []byte(\"siyuan:kek-verifier\"))\n\tif dErr != nil || string(decrypted) != string(kekVerifierMagic) {\n\t\treturn errors.New(Conf.Language(311)) // 主密码错误\n\t}\n\n\t// 校验 KEK 能解密现存笔记本和已删除笔记本历史中的 WrappedDEK，避免导入不匹配的备份。\n\tif !verifyKEKAgainstExistingBoxes(kek, nc) || !verifyKEKAgainstEncryptedHistory(kek, nc) {\n\t\treturn errors.New(Conf.Language(316)) // 密钥不匹配\n\t}\n\n\tnc.KDFParams = params // 确保写回 Conf 的参数已经通过完整校验。\n\tnc.Enabled = true\n\n\t// 先写 backup，再提交 conf；backup 失败时 conf 尚未改变，可重试\n\tif err := writeNotebookCryptoBackupData(nc, kek); err != nil {\n\t\treturn fmt.Errorf(\"failed to persist key backup: %w\", err)\n\t}\n\tConf.m.Lock()\n\t*Conf.NotebookCrypto = *nc\n\tConf.m.Unlock()\n\tConf.Save()\n\tIncSync()\n\treturn nil\n}\n\n// saveNotebookCryptoBackup 把当前 NotebookCrypto（含 MasterSalt/KEKVerifier/KDFParams）备份到 DataDir。\n// kek 必须非 nil：在 Checksum 定型后计算 KEKMAC 并落盘，保证恢复路径可通过 MAC 校验。\n// 无 KEK 生成的备份 KEKMAC 必为空，会被 deriveKEK/恢复路径拒绝，等于制造无法解锁的状态（详见设计 §19）。\nfunc saveNotebookCryptoBackup(kek []byte) error {\n\tif kek == nil {\n\t\t// 无 KEK 时不得生成当前格式备份：KEKMAC 缺失会被 deriveKEK/恢复路径拒绝，\n\t\t// 生成即等于制造无法解锁的状态。\n\t\treturn errors.New(\"cannot generate notebook crypto backup without KEK\")\n\t}","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/crypto.go#L347-L383","documentation":"ImportNotebookCryptoBackup wraps any error from writeNotebookCryptoBackupData as \"failed to persist key backup: %w\". The backup file is written BEFORE the in-memory Conf is committed (so a failure here leaves Conf unchanged and the import can be retried safely); this error means the validated backup could not be durably written to <DataDir>/.siyuan/data-crypto-backup.json.","triggerScenarios":"ImportNotebookCryptoBackup reaches the persist step after all password/key checks pass, but the underlying mkdir, marshal, or atomicWriteFile of the backup file fails — typically disk full, permission denied on the data directory, or antivirus/file-lock interference on Windows.","commonSituations":"Full disk during restore on a new device; read-only or permission-restricted data directory (e.g. restored from an archive with wrong ownership); security software holding the target file; failing disk.","solutions":["Check free disk space in the workspace/data partition and free space if needed","Verify write permissions on <DataDir>/.siyuan/ (and file ownership after archive-based restores)","Check whether antivirus or backup software locks the target file, then retry","Retry the import — Conf was not modified, so the operation is safe to repeat","If it persists, inspect the wrapped cause (%w) from the logs for the exact OS error"],"exampleFix":"# before\n$ ls -l <DataDir>/.siyuan  # owned by root, not writable\n# after\n$ sudo chown -R $USER <DataDir>/.siyuan\n$ # retry the import","handlingStrategy":"retry","validationCode":"// Pre-flight before import:\nfi, err := os.Stat(dataDir + \"/.siyuan\")\nif err != nil || !fi.IsDir() { return errors.New(\"data dir not writable/missing\") }\nif err := unix.Access(dataDir+\"/.siyuan\", unix.W_OK); err != nil { return errors.New(\"no write permission\") }","typeGuard":"null","tryCatchPattern":"if err := ImportNotebookCryptoBackup(raw, password); err != nil {\n    var unwrapped error = err\n    if strings.Contains(err.Error(), \"failed to persist key backup\") {\n        // safe to retry: Conf was not modified\n        log.Printf(\"persist failed, retry after fixing disk/permissions: %v\", unwrapped)\n    }\n}","preventionTips":["Monitor free disk space on the data volume before recovery operations","Ensure the data directory is writable by the kernel process user","Exclude the backup file from antivirus real-time locking where possible","Because Conf is committed only after the backup succeeds, always retry rather than switching strategies"],"tags":["filesystem","backup-persist","disk","permissions"],"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"}