{"record":{"id":"49a1565d6960f1a2","repo":"siyuan-note/siyuan","slug":"enable-encrypted-notebook-failed-failed-to-persis","errorCode":null,"errorMessage":"enable encrypted notebook failed: failed to persist key backup: %w","messagePattern":"enable encrypted notebook failed: failed to persist key backup: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1055,"sourceCode":"\t}\n\n\tConf.m.Lock()\n\tprevious := *Conf.NotebookCrypto\n\tConf.NotebookCrypto.Enabled = true\n\tConf.NotebookCrypto.MasterSalt = salt\n\tConf.NotebookCrypto.KDFParams = params\n\tConf.NotebookCrypto.KEKVerifier = verifierCT\n\tConf.NotebookCrypto.VerifierNonce = verifierNonce\n\tConf.m.Unlock()\n\n\t// 先持久化恢复备份，再提交 conf。此时尚无加密笔记本和历史依赖，任一步失败都不会孤立既有密文。\n\tif err := saveNotebookCryptoBackup(kek); err != nil {\n\t\t// 备份写失败则恢复启用前的内存配置；conf 尚未写入，无需再执行磁盘回滚。\n\t\tlogging.LogErrorf(\"save notebook crypto backup failed: %s\", err)\n\t\tConf.m.Lock()\n\t\t*Conf.NotebookCrypto = previous\n\t\tConf.m.Unlock()\n\t\treturn fmt.Errorf(\"enable encrypted notebook failed: failed to persist key backup: %w\", err)\n\t}\n\t// Conf.Save 内部会加 Conf.m，不能在持锁状态下调用（RWMutex 不可重入）。\n\t// 即使配置写入失败，已落盘的备份仍可在下次启动时恢复同一套密钥材料。\n\tConf.Save()\n\tIncSync()\n\treturn nil\n}\n\n// DisableEncryptedNotebook 关闭加密笔记本功能。前置：不能有加密笔记本存在，\n// 且不能有依赖当前密钥备份的已删除笔记本历史（否则禁用并删除备份会让这些历史永久锁死，违反 §19）。\n// 清除全局加密配置（MasterSalt/KEKVerifier），KEK/DEK 不再可用。\nfunc DisableEncryptedNotebook() error {\n\tnotebookCryptoMu.Lock()\n\tdefer notebookCryptoMu.Unlock()\n\n\t// 检查是否还有加密笔记本（含 conf 损坏但存在备份的）\n\tids, listErr := listAllEncryptedBoxIDs()\n\tif listErr != nil {","sourceCodeStart":1037,"sourceCodeEnd":1073,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L1037-L1073","documentation":"Thrown by EnableEncryptedNotebook (crypto.go:1055) when saveNotebookCryptoBackup fails while persisting the freshly generated key backup during initial feature enablement. The function rolls back the in-memory NotebookCrypto to its pre-enable snapshot (conf has not been written yet) and returns the wrapped I/O error. This guard exists so that a half-written backup can never leave the system believing encryption is enabled without a recoverable key backup.","triggerScenarios":"First-time enable flow: salt generated, KEK derived, verifier encrypted, Conf.NotebookCrypto mutated in memory, then saveNotebookCryptoBackup(kek) returns a non-nil error. The backup write goes to dataCryptoBackupPath(); it fails on disk-full, permission denied, read-only filesystem, path-too-long, or an existing read-only file lock.","commonSituations":"Data directory on a nearly-full disk, on a network/SMB mount with intermittent write permission, or on read-only media. A leftover lock or a permissions change after a system migration. Antivirus or sync client holding the backup file open exclusively on Windows.","solutions":["Free disk space on the data volume and confirm the data directory is writable by the kernel process, then retry enable.","Check and clear any stale lock on the backup path (dataCryptoBackupPath()) and verify file ownership/permissions.","If the data directory lives on a network mount, move it to a local writable volume or fix the mount's read/write permissions and retry.","Inspect the wrapped error chain (the %w tail names the syscall/path) to pinpoint the exact I/O failure."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Pre-flight: confirm the backup path is writable before enable.\nfunc backupPathWritable() error {\n    p := dataCryptoBackupPath()\n    f, err := os.CreateTemp(filepath.Dir(p), \"crypto-backup-probe-*\")\n    if err != nil {\n        return fmt.Errorf(\"backup dir not writable: %w\", err)\n    }\n    f.Close()\n    os.Remove(f.Name())\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := model.EnableEncryptedNotebook(password); err != nil {\n    if strings.Contains(err.Error(), \"failed to persist key backup\") {\n        // surface the wrapped I/O cause; advise freeing space / fixing permissions\n        respond(c, \"cannot write key backup: \"+err.Error())\n        return\n    }\n    respond(c, err.Error())\n}","preventionTips":["Ensure the data directory is on a writable local volume with adequate free space.","Run a write-probe on the data directory before enable.","Exclude the key backup path from exclusive-locking antivirus/sync tools on Windows."],"tags":["encryption","notebook-crypto","io","filesystem","config-persistence"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}