{"record":{"id":"a6b6d96a35dc56c0","repo":"siyuan-note/siyuan","slug":"stat-encrypted-notebook-history-backup-s-failed","errorCode":null,"errorMessage":"stat encrypted notebook history backup [%s] failed: %w","messagePattern":"stat encrypted notebook history backup \\[(.+?)\\] failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":939,"sourceCode":"func HasEncryptedNotebookHistory() bool {\n\thasHistory, err := scanEncryptedNotebookHistory()\n\tif err != nil {\n\t\tlogging.LogErrorf(\"scan encrypted notebook history failed: %s\", err)\n\t\treturn true\n\t}\n\treturn hasHistory\n}\n\n// isEncryptedHistoryBoxDir 判断历史目录中的 boxID 子目录是否属于加密笔记本。\n// 优先看 notebook-crypto-backup.json（删除前随 box 目录整体备份，是加密身份的权威标识），\n// 再 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}","sourceCodeStart":921,"sourceCodeEnd":957,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L921-L957","documentation":"isEncryptedHistoryBoxDir decides whether a history box dir is an encrypted notebook's box by probing .siyuan/notebook-crypto-backup.json. A failed os.Stat that is not NotExist is wrapped as 'stat encrypted notebook history backup [%s] failed'. The library throws it because an inaccessible backup marker must abort classification rather than classify the box as unencrypted (which could authorize destructive operations on recovery material).","triggerScenarios":"encryptedNotebookHistoryBoxDirs, hasEncryptedNotebookDeleteHistory, or IsEncryptedHistoryPath statting the backup file when permission is denied on the box dir or its .siyuan subdir, the path component is not a directory, or the filesystem returns an I/O error (e.g. EXDEV/ESTALE on network mounts).","commonSituations":"Snapshots restored with root ownership; .siyuan replaced by a file; corrupted network-share mounts; macOS/Windows ACLs blocking the kernel process after workspace moves between machines.","solutions":["Read the wrapped OS error and fix access to <boxDir>/.siyuan/notebook-crypto-backup.json (chmod/chown or ACLs)","Verify .siyuan is still a directory and not a file/symlink; restore it from backup if broken","Re-mount or repair the storage if the error is I/O level (ESTALE, EIO)","Retry the scan after fixing; do not delete or rewrite history boxes while the error persists"],"exampleFix":"// before: unreadable .siyuan in snapshot box\nls -l data/history/20260901-delete/20240101120000-abc123/\n# .siyuan  root:root 000\n// after\nsudo chmod -R u+rwX data/history/20260901-delete/20240101120000-abc123/.siyuan\nsudo chown -R $USER data/history/20260901-delete/20240101120000-abc123/.siyuan","handlingStrategy":"try-catch","validationCode":"p := filepath.Join(boxDir, \".siyuan\", \"notebook-crypto-backup.json\")\nif _, err := os.Stat(p); err != nil && !os.IsNotExist(err) { return err }","typeGuard":"func backupStatable(boxDir string) bool { _, err := os.Stat(filepath.Join(boxDir, \".siyuan\", \"notebook-crypto-backup.json\")); return err == nil || os.IsNotExist(err) }","tryCatchPattern":"enc, err := isEncryptedHistoryBoxDir(boxDir)\nif err != nil {\n    // unknown status: treat as encrypted to protect recovery material\n    handleAsEncrypted(boxDir)\n    return\n}","preventionTips":["Restore snapshots with the same owner/permissions as the kernel process","Keep .siyuan a directory — never overwrite it with a file","Repair ACLs after cross-machine workspace moves (macOS/Windows)","Treat stat errors on history boxes as 'unknown, assume encrypted'"],"tags":["filesystem","permissions","history","encryption"],"backgroundTag":"permission-denied","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"}