siyuan-note/siyuan · error
read history dir failed: %w
Error message
read history dir failed: %w
What it means
encryptedNotebookHistoryBoxDirs enumerates history snapshot directories under util.HistoryDir to find encrypted notebook box dirs. If os.ReadDir on the history dir fails with any error other than NotExist, it is wrapped as 'read history dir failed'. The library throws it to surface underlying I/O problems instead of silently treating history as absent, because callers rely on this scan to decide whether recovery material exists.
Source
Thrown at kernel/model/crypto.go:793
// (box 目录已删)。因此 DisableEncryptedNotebook 不能只靠 ListAllEncryptedBoxIDs 判定——
// 已删除加密笔记本的历史仍依赖当前 MasterSalt/KEKVerifier 才能恢复,禁用并删除备份会让这些
// 历史永久锁死,违反设计 §19。本函数扫描历史目录识别这类依赖。
//
// 判定信号:历史条目 <HistoryDir>/<ts>-<op>/<boxID>/.siyuan/ 下存在
// notebook-crypto-backup.json(专为 box 删除后的恢复设计),或 conf.json 标记 Encrypted=true。
// boxID 用 ast.IsNodeIDPattern 校验,避免误判 assets/storage 等非 box 目录。
func scanEncryptedNotebookHistory() (bool, error) {
boxDirs, err := encryptedNotebookHistoryBoxDirs()
return len(boxDirs) > 0, err
}
func encryptedNotebookHistoryBoxDirs() (ret []string, err error) {
entries, err := os.ReadDir(util.HistoryDir)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("read history dir failed: %w", err)
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
// 历史快照目录:<ts>-<op>,其下是各 boxID 子目录
snapshotDir := filepath.Join(util.HistoryDir, entry.Name())
boxEntries, readErr := os.ReadDir(snapshotDir)
if readErr != nil {
return nil, fmt.Errorf("read history snapshot [%s] failed: %w", entry.Name(), readErr)
}
for _, boxEntry := range boxEntries {
if !boxEntry.IsDir() || !ast.IsNodeIDPattern(boxEntry.Name()) {
continue
}
encrypted, checkErr := isEncryptedHistoryBoxDir(filepath.Join(snapshotDir, boxEntry.Name()))
if checkErr != nil {
return nil, checkErrView on GitHub (pinned to 8641553a1f)
Solutions
- Check the wrapped cause (%w) with errors.Unwrap / %v to see the OS error; fix that root cause first
- Verify util.HistoryDir (workspace/data/history) exists and is a directory: ls -la it, and ensure it is not a file
- Fix permissions so the kernel process user has read access to the history directory (chmod/chown)
- If the history dir is corrupt or a stray file, move it aside and let SiYuan recreate it (after backing it up — it holds recovery material)
Example fix
// before: history path is a regular file, ReadDir fails // data/history -> plain file // after: ensure it is a directory rm data/history mkdir -p data/history
Defensive patterns
Strategy: try-catch
Validate before calling
if fi, err := os.Stat(util.HistoryDir); err != nil || !fi.IsDir() { return fmt.Errorf("history dir unusable: %w", err) } Type guard
func historyDirUsable(p string) bool { fi, err := os.Stat(p); return err == nil && fi.IsDir() } Try / catch
dirs, err := encryptedNotebookHistoryBoxDirs()
if err != nil {
var pe *os.PathError
if errors.As(err, &pe) { log.Printf("history scan blocked at %s: %v", pe.Path, pe.Err) }
// treat as 'history presence unknown': do NOT delete recovery material
return
} Prevention
- Keep data/history as a directory owned by the kernel process user
- Avoid pointing the workspace at removable/network drives without remount supervision
- Never replace history with a file or symlink during restores
- Check HasEncryptedNotebookHistory semantics: on scan error it assumes history exists
When it happens
Trigger: Calling scanEncryptedNotebookHistory, verifyKEKAgainstEncryptedHistory or HasEncryptedNotebookHistory when kernel/util.HistoryDir exists but cannot be listed: permission denied on the directory, a path component is actually a file, or a low-level I/O error (bad sectors, network drive drop). os.IsNotExist errors are deliberately returned as nil, so this error only fires for other failures.
Common situations: Running SiYuan with a workspace on a removable/network drive that got disconnected; the data/history path was replaced by a regular file after a botched restore; restrictive file permissions after copying a workspace between users or containers; antivirus or backup software holding locks on Windows.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- read session file failed: %w
- create session dir failed: %w
- save session file failed: %w
- read history snapshot [%s] failed: %w
- read encrypted notebook history conf [%s] failed: %w
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/c181a65f60542ce7.
Report an issue: GitHub.