{"record":{"id":"f6817a8d749bbadf","repo":"siyuan-note/siyuan","slug":"marshal-notebook-crypto-backup-failed-w","errorCode":null,"errorMessage":"marshal notebook crypto backup failed: %w","messagePattern":"marshal notebook crypto backup failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":404,"sourceCode":"\tprepareBackupForWrite(&nc)\n\tnc.KEKMAC = computeKEKMAC(&nc, kek)\n\tif !notebookCryptoConfigurationComplete(&nc) {\n\t\tConf.m.Unlock()\n\t\treturn errors.New(\"cannot save incomplete notebook crypto configuration\")\n\t}\n\tConf.NotebookCrypto.Spec = nc.Spec\n\tConf.NotebookCrypto.BackupID = nc.BackupID\n\tConf.NotebookCrypto.CreatedAt = nc.CreatedAt\n\tConf.NotebookCrypto.Checksum = nc.Checksum\n\tConf.NotebookCrypto.KEKMAC = nc.KEKMAC // 保持 Conf 与备份文件的 KEKMAC 一致\n\tConf.m.Unlock()\n\tbackupPath := dataCryptoBackupPath()\n\tif err := os.MkdirAll(filepath.Dir(backupPath), 0755); err != nil {\n\t\treturn fmt.Errorf(\"mkdir notebook crypto backup dir failed: %w\", err)\n\t}\n\tdata, err := json.Marshal(nc)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal notebook crypto backup failed: %w\", err)\n\t}\n\tif err := atomicWriteFile(backupPath, data); err != nil {\n\t\treturn fmt.Errorf(\"write notebook crypto backup failed: %w\", err)\n\t}\n\treturn nil\n}\n\n// writeNotebookCryptoBackupData 将指定的 NotebookCrypto 写入备份文件（不依赖 Conf.NotebookCrypto）。\n// kek 必须非 nil：在 Checksum 定型后计算 KEKMAC，保证落盘 MAC 与落盘内容一致。\nfunc writeNotebookCryptoBackupData(nc *conf.NotebookCrypto, kek []byte) error {\n\tif kek == nil {\n\t\treturn errors.New(\"cannot generate notebook crypto backup without KEK\")\n\t}\n\tprepareBackupForWrite(nc)\n\tnc.KEKMAC = computeKEKMAC(nc, kek)\n\tif !notebookCryptoConfigurationComplete(nc) {\n\t\treturn errors.New(\"cannot write incomplete notebook crypto backup\")\n\t}","sourceCodeStart":386,"sourceCodeEnd":422,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L386-L422","documentation":"saveNotebookCryptoBackup marshals the NotebookCrypto configuration to JSON before the atomic write; this error wraps any json.Marshal failure. This is nearly impossible in practice because the struct contains only JSON-serializable fields, but if it happens (e.g. an unsupported field type introduced by code change or corruption) no backup is written.","triggerScenarios":"json.Marshal(nc) returns an error inside saveNotebookCryptoBackup, called from EnableEncryptedNotebook, deriveKEK, or ChangeMasterPassword.","commonSituations":"A custom marshaler on conf.NotebookCrypto panicking or returning an error; an incompatible struct change (e.g. a func or channel field added) after a code upgrade; extremely rare in a released build.","solutions":["Check for modifications or forks of conf.NotebookCrypto that introduce unserializable fields","Ensure no plugin/patch overrides MarshalJSON for NotebookCrypto","Retry after restoring a stock kernel build; if reproducible, capture the wrapped inner error for a bug report"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if _, err := json.Marshal(Conf.NotebookCrypto); err != nil {\n    // config struct not serializable; do not attempt backup save\n}\n","typeGuard":null,"tryCatchPattern":"if err := saveNotebookCryptoBackup(kek); err != nil {\n    if strings.Contains(err.Error(), \"marshal notebook crypto backup failed\") {\n        // inspect wrapped inner error; suspect struct/marshaler changes\n    }\n}\n","preventionTips":["Keep conf.NotebookCrypto limited to JSON-serializable fields","Avoid custom MarshalJSON overrides on crypto config structs","Test backup save paths after any struct change to conf.NotebookCrypto"],"tags":["crypto","json","serialization","backup","internal"],"backgroundTag":"json-marshal-failed","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}