siyuan-note/siyuan · error
read Markdown [ ]
Error message
read Markdown [%s]: %w
What it means
SiYuan's Obsidian vault importer wraps any error that occurs while opening/reading a source Markdown file with this message, carrying the file's vault-relative path and the underlying cause. newObsidianReadUserError maps the cause to localized user-facing message 345 (or 346 when the file changed mid-import via errObsidianSourceChanged). It is thrown so users see which specific file could not be read rather than a bare OS error.
Solutions
- Check the wrapped cause (%w) to see if it is a permission, not-exist, or source-changed error and fix that root cause
- Verify the file exists and is readable at the reported path; re-sync cloud placeholders so the real content is local
- Close Obsidian / pause sync clients, then retry the import
- If errObsidianSourceChanged is the cause, re-run the import so the analysis is redone against the current file state
Defensive patterns
Strategy: try-catch
Validate before calling
if _, err := os.Stat(absPath); err != nil { /* file missing/unreadable before import */ }
if info, err := os.Stat(absPath); err == nil && info.IsDir() { /* skip non-files */ } Try / catch
if err != nil {
if errors.Is(err, errObsidianSourceChanged) { /* re-run import */ }
if os.IsNotExist(errors.Unwrap(err)) { /* restore/resync the file */ }
if os.IsPermission(errors.Unwrap(err)) { /* fix permissions */ }
} Prevention
- Keep the vault on local disk, not cloud placeholder folders
- Close Obsidian and pause sync clients before importing
- Verify all files are readable by the SiYuan process user
When it happens
Trigger: os.Open(file.AbsPath) or subsequent reads fail during importObsidianVault processing — e.g. the file was deleted between vault scan and read, permissions deny access, the path is too long, or a read race detected as errObsidianSourceChanged.
Common situations: Vault lives on a network/Cloud drive that dropped; file is locked by Obsidian or a sync client (iCloud/OneDrive placeholder files); OS permission changes; file removed by another sync tool during import.
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@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/643a139bcc4bfae9.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/import_obsidian.go:953
if err != nil {
return nil, err
}
after, err := os.Stat(file.AbsPath)
if err != nil {
return nil, err
}
if before.Size() != after.Size() || !before.ModTime().Equal(after.ModTime()) {
return nil, fmt.Errorf("%w while reading", errObsidianSourceChanged)
}
return data, nil
}
func newObsidianReadUserError(file *obsidianSourceFile, err error) error {
language := 345
if errors.Is(err, errObsidianSourceChanged) {
language = 346
}
return newObsidianUserError(language, file.RelPath, fmt.Errorf("read Markdown [%s]: %w", file.RelPath, err))
}
func validateObsidianReadableFile(file *obsidianSourceFile) error {
opened, err := os.Open(file.AbsPath)
if err != nil {
return err
}
defer opened.Close()
buffer := make([]byte, 1)
_, err = opened.Read(buffer)
if err != nil && !errors.Is(err, io.EOF) {
return err
}
return validateObsidianSourceMetadata(file)
}
func buildObsidianHeadingIndex(doc *obsidianDocPlan, tree *parse.Tree) {
stack := make([]string, 6)View on GitHub (pinned to 9f775e8a12)