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, checkErr

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Check the wrapped cause (%w) with errors.Unwrap / %v to see the OS error; fix that root cause first
  2. Verify util.HistoryDir (workspace/data/history) exists and is a directory: ls -la it, and ensure it is not a file
  3. Fix permissions so the kernel process user has read access to the history directory (chmod/chown)
  4. 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

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


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/c181a65f60542ce7. Report an issue: GitHub.