{"record":{"id":"72d4b1545cb20b33","repo":"siyuan-note/siyuan","slug":"parse-encrypted-notebook-history-conf-s-failed","errorCode":null,"errorMessage":"parse encrypted notebook history conf [%s] failed: %w","messagePattern":"parse encrypted notebook history conf \\[(.+?)\\] failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":954,"sourceCode":"\tif _, err := os.Stat(backupPath); err == nil {\n\t\treturn true, nil\n\t} else if !os.IsNotExist(err) {\n\t\treturn false, fmt.Errorf(\"stat encrypted notebook history backup [%s] failed: %w\", boxDir, err)\n\t}\n\tconfPath := filepath.Join(siyuanDir, \"conf.json\")\n\tif _, err := os.Stat(confPath); err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, fmt.Errorf(\"stat encrypted notebook history conf [%s] failed: %w\", boxDir, err)\n\t}\n\tdata, err := filelock.ReadFile(confPath)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"read encrypted notebook history conf [%s] failed: %w\", boxDir, err)\n\t}\n\tvar boxConf conf.BoxConf\n\tif err = gulu.JSON.UnmarshalJSON(data, &boxConf); err != nil {\n\t\treturn false, fmt.Errorf(\"parse encrypted notebook history conf [%s] failed: %w\", boxDir, err)\n\t}\n\treturn boxConf.Encrypted, nil\n}\n\n// cachedDEKs 缓存已解锁加密笔记本的 DEK，按 boxID 索引。\n// KEK 不全局缓存（\"严格每笔记本单独解锁\"语义）：UnlockBox 临时派生 KEK 解出 DEK 后即丢弃 KEK，\n// 仅保留 per-box DEK 供后续读写加解密。\nvar (\n\tcachedDEKs     = map[string][]byte{}\n\tcachedDEKsLock sync.RWMutex\n)\n\n// boxLastAccess 记录每个加密笔记本最近一次真实用户交互或显式保活时间（unix 纳秒），供自动锁定 cron 使用。\n// key: boxID, value: *atomic.Int64。UnlockBox 成功时初始化，Unmount 时清理。\nvar boxLastAccess sync.Map\n\n// EnableEncryptedNotebookWithSync 在启用前先完成同步；同步恢复了既有配置时只校验原主密码。\nfunc EnableEncryptedNotebookWithSync(password string) error {","sourceCodeStart":936,"sourceCodeEnd":972,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L936-L972","documentation":"After a successful read, isEncryptedHistoryBoxDir unmarshals conf.json into conf.BoxConf; invalid JSON is wrapped as 'parse encrypted notebook history conf [%s] failed'. The library throws it because a malformed BoxConf cannot yield a trustworthy Encrypted flag, and guessing 'false' could expose encrypted recovery material to destructive history operations.","triggerScenarios":"Any caller of isEncryptedHistoryBoxDir (encryptedNotebookHistoryBoxDirs, hasEncryptedNotebookDeleteHistory, IsEncryptedHistoryPath) processing a conf.json that is truncated, empty, binary garbage, or hand-edited invalid JSON — typically after an interrupted write, disk-full condition, or manual edit.","commonSituations":"Kernel killed mid-write to conf.json; full disk producing zero-byte files; users editing conf.json with a text editor and breaking JSON syntax; sync conflict merges concatenating two JSON documents.","solutions":["Open the conf named in [%s] and validate its JSON (jq or any JSON linter) to see the exact syntax error","Restore a valid conf.json from the live notebook's .siyuan/conf.json or from backup/sync history","If only the Encrypted flag matters and the file is beyond repair, reconstruct a minimal valid BoxConf JSON with the correct fields","Fix the write path (free disk space, stop killing the process) so future writes are atomic/complete"],"exampleFix":"// before: hand-edited conf.json\n{ \"name\": \"My notebook\", \"encrypted\": true, }\n// after: valid JSON, no trailing comma\n{ \"name\": \"My notebook\", \"encrypted\": true }","handlingStrategy":"validation","validationCode":"data, err := filelock.ReadFile(confPath)\nif err == nil {\n    var probe map[string]any\n    if err := json.Unmarshal(data, &probe); err != nil { return fmt.Errorf(\"conf.json invalid: %w\", err) }\n}","typeGuard":"func validBoxConfJSON(data []byte) bool { var c conf.BoxConf; return gulu.JSON.UnmarshalJSON(data, &c) == nil }","tryCatchPattern":"enc, err := isEncryptedHistoryBoxDir(boxDir)\nif err != nil && errors.Is(errors.Unwrap(err), jsonErrKind) {\n    return fmt.Errorf(\"restore valid conf.json for %s from backup before proceeding\", boxDir)\n}","preventionTips":["Never hand-edit conf.json while the kernel is running","Ensure adequate free disk space so writes are not truncated","Validate conf.json with a JSON parser after manual edits or sync merges","Restore conf.json from the live notebook or sync history when corrupted"],"tags":["json","parse","history","conf"],"backgroundTag":"json-unmarshal-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"}