{"record":{"id":"1d0169af39d1744d","repo":"siyuan-note/siyuan","slug":"marshal-notebook-crypt-backup-failed-w","errorCode":null,"errorMessage":"marshal notebook crypt backup failed: %w","messagePattern":"marshal notebook crypt backup failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":2465,"sourceCode":"\treturn filepath.Join(util.DataDir, boxID, \".siyuan\", notebookCryptoBackupFilename)\n}\n\n// writeNotebookCryptBackup 写入加密笔记本的 BoxCrypt 备份。\n// 仅在 Encrypted=true 的笔记本上调用，配合 CreateEncryptedBox / ChangeMasterPassword 写入。\nfunc writeNotebookCryptBackup(boxID string, crypt *conf.BoxEncryption) error {\n\tif !ast.IsNodeIDPattern(boxID) {\n\t\treturn errors.New(\"invalid notebook ID\")\n\t}\n\tif err := validateBoxEncryption(crypt); err != nil {\n\t\treturn err\n\t}\n\tbackupPath := notebookCryptoBackupPath(boxID)\n\tif err := os.MkdirAll(filepath.Dir(backupPath), 0755); err != nil {\n\t\treturn fmt.Errorf(\"mkdir notebook crypt backup dir failed: %w\", err)\n\t}\n\tdata, err := gulu.JSON.MarshalIndentJSON(crypt, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal notebook crypt backup failed: %w\", err)\n\t}\n\tif err := filelock.WriteFile(backupPath, data); err != nil {\n\t\treturn fmt.Errorf(\"write notebook crypt backup failed: %w\", err)\n\t}\n\treturn nil\n}\n\n// readNotebookCryptBackup 读取加密笔记本的 BoxCrypt 备份。\n// 备份文件不存在时返回 (nil, nil)，调用方据此区分\"非加密笔记本\"和\"备份不存在\"。\nfunc readNotebookCryptBackup(boxID string) (*conf.BoxEncryption, error) {\n\tif !ast.IsNodeIDPattern(boxID) {\n\t\treturn nil, errors.New(\"invalid notebook ID\")\n\t}\n\tbackupPath := notebookCryptoBackupPath(boxID)\n\tif !filelock.IsExist(backupPath) {\n\t\treturn nil, nil\n\t}\n\treturn readBoxEncryptionFile(backupPath)","sourceCodeStart":2447,"sourceCodeEnd":2483,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/model/crypto.go#L2447-L2483","documentation":"Wrapped error from gulu.JSON.MarshalIndentJSON while serializing the conf.BoxEncryption struct (WrappedDEK, WrapNonce, KDF params) for the per-notebook backup. Since BoxEncryption is a plain data struct, a marshal failure is essentially unreachable in practice and signals a programming error (unsupported field type added to the struct) rather than an environment problem.","triggerScenarios":"A future field of type chan/func/complex added to conf.BoxEncryption; a custom fork that embeds unmarshalable types in the struct.","commonSituations":"Almost exclusively hit by developers modifying the struct; not a runtime condition users encounter.","solutions":["Check the wrapped error for json.UnsupportedTypeError and remove/replace the offending field type in conf.BoxEncryption","Keep BoxEncryption limited to strings, numbers and byte slices","Treat this error as a build-time bug: add a unit test asserting MarshalIndentJSON succeeds on a valid validateBoxEncryption sample"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"var typeErr *json.UnsupportedTypeError\nif errors.As(err, &typeErr) {\n    // struct contains an unmarshalable field: fix conf.BoxEncryption","preventionTips":["Keep conf.BoxEncryption composed only of JSON-safe field types","Add a round-trip marshal test for the encryption config structs in CI"],"tags":["serialization","json","backup","go"],"backgroundTag":"json-marshal-failed","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}