siyuan-note/siyuan · error

read document [%s] failed: %w

Error message

read document [%s] failed: %w

What it means

generateDocHistoryFile reads a document .sy file before generating its history snapshot. If the read fails with an error other than os.IsNotExist (a missing file is intentionally treated as nothing-to-do), the failure is wrapped as "read document [%s] failed" including the file path. This guards the doc-history pipeline against unreadable or corrupted .sy sources.

Source

Thrown at kernel/model/history.go:978

	luteEngine := util.NewLute()
	for _, file := range files {
		if err = generateDocHistoryFile(box.ID, file, historyDir, luteEngine); err != nil {
			logging.LogErrorf("generate history failed: %s", err)
			return
		}
	}

	indexHistoryDir(filepath.Base(historyDir), util.NewLute())
	return
}

func generateDocHistoryFile(boxID, file, historyDir string, luteEngine *lute.Lute) error {
	data, err := filelock.ReadFile(file)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return fmt.Errorf("read document [%s] failed: %w", file, err)
	}

	return generateDocHistoryFromData(boxID, file, historyDir, data, luteEngine)
}

func generateDocHistoryFromData(boxID, file, historyDir string, data []byte, luteEngine *lute.Lute) error {
	historyPath := filepath.Join(historyDir, boxID, strings.TrimPrefix(file, filepath.Join(util.DataDir, boxID)))
	if err := os.MkdirAll(filepath.Dir(historyPath), 0755); err != nil {
		return err
	}
	if err := gulu.File.WriteFileSafer(historyPath, data, 0644); err != nil {
		return err
	}
	if !strings.HasSuffix(file, ".sy") {
		return nil
	}

	tree, err := loadTreeByData(file, data, luteEngine)

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Check filesystem permissions/ownership of the .sy file named in the message and restore read access
  2. Verify disk health and free space; retry the operation after transient IO errors
  3. Restore the .sy file from sync/backup if it is corrupted; missing files are tolerated but unreadable ones are not
  4. Re-index or re-open the notebook so history generation retries with a valid document tree
Defensive patterns

Strategy: try-catch

Validate before calling

const fs = require("fs");
try {
  fs.accessSync(syPath, fs.constants.R_OK);
} catch (e) {
  if (e.code === "ENOENT") return; // tolerated by the kernel
  throw new Error("document unreadable: " + syPath);
}

Try / catch

try {
  await triggerDocHistory(boxID, syPath);
} catch (e) {
  if (/read document \[.+\] failed/.test(e.message)) {
    // restore the .sy file from sync/backup and retry
  }
  throw e;
}

Prevention

When it happens

Trigger: generateDocHistory0 (invoked during doc-history generation, e.g. on document update/delete) hitting a .sy file that exists but cannot be read: permission denied, IO error, file locked, or truncated/corrupted content returning a non-NotExist error.

Common situations: Workspace files copied as root changing ownership; sync conflicts leaving unreadable .sy files; disk errors; antivirus locking the file during read; mobile/mobile-storage IO glitches.

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/aa2ba0aa7b2f5c77. Report an issue: GitHub.