{"record":{"id":"4b2d0e4fd17d6a9e","repo":"siyuan-note/siyuan","slug":"unmarshal-notebook-crypt-backup-failed-w","errorCode":null,"errorMessage":"unmarshal notebook crypt backup failed: %w","messagePattern":"unmarshal notebook crypt backup failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":2493,"sourceCode":"func 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)\n}\n\nfunc readBoxEncryptionFile(backupPath string) (*conf.BoxEncryption, error) {\n\tdata, err := filelock.ReadFile(backupPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read notebook crypt backup failed: %w\", err)\n\t}\n\tvar crypt conf.BoxEncryption\n\tif err = gulu.JSON.UnmarshalJSON(data, &crypt); err != nil {\n\t\treturn nil, fmt.Errorf(\"unmarshal notebook crypt backup failed: %w\", err)\n\t}\n\tif err = validateBoxEncryption(&crypt); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &crypt, nil\n}\n\n// copyAssetDecryptIfEncrypted 把 srcPath 的 asset 复制到 destPath。\n// 若 srcPath 在已解锁的加密笔记本下，读密文→解密→写明文到 destPath（导出目录）；\n// 否则走 filelock.Copy 原路径（字节级复制，密文/明文均可）。\nfunc copyAssetDecryptIfEncrypted(srcPath, destPath string) error {\n\tif err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {\n\t\treturn err\n\t}\n\n\tboxID := ExtractBoxIDFromAssetsPath(srcPath)\n\tif boxID != \"\" && IsEncryptedBox(boxID) {\n\t\tHoldBoxReadLock(boxID)","sourceCodeStart":2475,"sourceCodeEnd":2511,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/model/crypto.go#L2475-L2511","documentation":"Wrapped error from gulu.JSON.UnmarshalJSON while parsing the notebook crypto backup file. The bytes were read successfully but are not a valid conf.BoxEncryption JSON — hand-edited, truncated, zero-filled (sparsed by sync tools), or written by an incompatible version with a different schema.","triggerScenarios":"readNotebookCryptBackup on a notebook-crypto-backup.json that was edited manually or corrupted; files truncated by interrupted writes; a workspace synced via a tool that placeholder-izes files.","commonSituations":"Users hand-editing JSON to 'fix' encryption; cloud-sync placeholder files materialized as zeros; version mismatches after downgrades.","solutions":["Restore the backup file from a workspace snapshot (history/backup tooling)","Reconstruct it from conf.json: copy the BoxCrypt object of that notebook into .siyuan/notebook-crypto-backup.json with correct shape, then run validateBoxEncryption-compatible unlock","As a last resort, decrypt-and-recreate: unlock with the known-good conf.json copy, remove the broken backup, and run ChangeMasterPassword to regenerate both files"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil {\n    if strings.Contains(err.Error(), \"unmarshal notebook crypt backup failed\") {\n        // reconstruct backup from conf.json BoxCrypt or restore from snapshot, then retry unlock","preventionTips":["Never hand-edit notebook-crypto-backup.json","Keep version-consistent backups: copy conf.json and the backup file together","Beware cloud-sync placeholder files materializing as zero-length JSON"],"tags":["serialization","json","backup","corruption","go"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}