{"record":{"id":"716f6d09af9717d4","repo":"siyuan-note/siyuan","slug":"read-encrypted-notebook-history-conf-s-failed","errorCode":null,"errorMessage":"read encrypted notebook history conf [%s] failed: %w","messagePattern":"read encrypted notebook history conf \\[(.+?)\\] failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":950,"sourceCode":"// 再 fallback 到 conf.json 的 Encrypted 标志。\nfunc isEncryptedHistoryBoxDir(boxDir string) (bool, error) {\n\tsiyuanDir := filepath.Join(boxDir, \".siyuan\")\n\tbackupPath := filepath.Join(siyuanDir, \"notebook-crypto-backup.json\")\n\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 时清理。","sourceCodeStart":932,"sourceCodeEnd":968,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L932-L968","documentation":"isEncryptedHistoryBoxDir reads .siyuan/conf.json via filelock.ReadFile; a failed read is wrapped as 'read encrypted notebook history conf [%s] failed'. The library throws it because the BoxConf determines whether the history box holds encrypted material — a read failure must abort rather than default to 'unencrypted', which could permit unsafe deletion of recovery material.","triggerScenarios":"Callers of isEncryptedHistoryBoxDir hitting read errors on conf.json: the file is locked exclusively by another process, permission denied at open time (stat succeeded, open fails), or filelock failing on the underlying file (removed between stat and read, or filesystem-level I/O error).","commonSituations":"Two kernel instances (or a kernel plus a tooling script) running on the same workspace; the file deleted by history pruning between the stat and read; disk errors or a forcibly-unmounted network volume.","solutions":["Close other SiYuan instances/scripts locking the workspace, then retry","Verify conf.json still exists and is readable after the stat (fix the delete race by pausing history cleanup)","Read the wrapped cause; if it is an I/O/mount error, repair the storage first","Restore conf.json from backup if the file itself is unreadable or truncated"],"exampleFix":"// before: second kernel instance locks the workspace\npkill -f siyuan-kernel  # all instances\n// after: keep exactly one instance owning workspace locks\nsystemctl --user stop siyuan-old; run new kernel; retry scan","handlingStrategy":"retry","validationCode":"if _, err := os.Stat(confPath); err == nil {\n    if f, err := os.Open(confPath); err != nil { return err } else { f.Close() }\n}","typeGuard":null,"tryCatchPattern":"enc, err := isEncryptedHistoryBoxDir(boxDir)\nif err != nil && isLockOrTransient(err) {\n    time.Sleep(100 * time.Millisecond)\n    enc, err = isEncryptedHistoryBoxDir(boxDir) // retry once after releasing other instances\n}","preventionTips":["Run only one kernel instance per workspace","Pause history-pruning jobs during scans so files are not deleted mid-read","Ensure filelock compatibility of the storage (avoid filesystems without proper locking, e.g. some NFS setups)","Retry transient read failures before concluding the box is unencrypted"],"tags":["filesystem","file-lock","io","history"],"backgroundTag":"file-read-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"}