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
- Check filesystem permissions/ownership of the .sy file named in the message and restore read access
- Verify disk health and free space; retry the operation after transient IO errors
- Restore the .sy file from sync/backup if it is corrupted; missing files are tolerated but unreadable ones are not
- 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
- Keep workspace files owned/readable by the kernel process user
- Restore corrupted .sy files from sync or history before editing again
- Avoid writing .sy files with external tools while the kernel is running
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
- read history dir failed: %w
- read history snapshot [%s] failed: %w
- read encrypted notebook history conf [%s] failed: %w
- copy asset [%s] to [%s] failed: %w
- read inline styles failed: %w
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/aa2ba0aa7b2f5c77.
Report an issue: GitHub.