{"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":358,"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) || !verifyKEKAgainstEncryptedHistory(kek) {\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":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L340-L376","documentation":"ImportNotebookCryptoBackup returns a wrapped error at line 358 when writeNotebookCryptoBackupData fails while persisting the validated backup to <DataDir>/.siyuan/data-crypto-backup.json. By this point every cryptographic check passed and the in-memory nc was prepared; the failure is purely I/O (mkdir, marshal, or atomic write). The error wraps the underlying cause with %w so callers can inspect it.","triggerScenarios":"writeNotebookCryptoBackupData returns an error: the backup directory cannot be created (os.MkdirAll fails), the JSON marshal fails, or atomicWriteFile fails. Causes include insufficient permissions on DataDir, read-only filesystem, disk full, or path resolution failure.","commonSituations":"DataDir on a read-only or full volume; permission/ownership change on the workspace; antivirus/lock preventing the temp write; removable media ejected mid-operation.","solutions":["Inspect the wrapped underlying error (errors.Unwrap / errors.Is) to identify mkdir vs write vs marshal.","Free disk space and verify write permissions on <DataDir>/.siyuan/.","Retry the import once the filesystem issue is resolved — conf has not been mutated yet at this point (the comment notes conf is unchanged on backup failure, so retry is safe)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Ensure DataDir is writable before importing.\ndir := filepath.Join(util.DataDir, \".siyuan\")\nif info, err := os.Stat(dir); err != nil || !info.IsDir() {\n    return errors.New(\"data dir not available\")\n}","typeGuard":null,"tryCatchPattern":"// Unwrap to distinguish mkdir vs write vs marshal failures.\nif err := model.ImportNotebookCryptoBackup(data, password); err != nil {\n    var inner error\n    if errors.As(err, &inner) {\n        log.Printf(\"persist failed: %v\", inner)\n    }\n}","preventionTips":["Verify write permission and free space on <DataDir>/.siyuan before importing.","Retry is safe: on persist failure conf has not been mutated yet.","Inspect the wrapped error to localize mkdir vs atomic-write vs marshal."],"tags":["crypto","backup","filesystem","io","persistence"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}