{"record":{"id":"643a139bcc4bfae9","repo":"siyuan-note/siyuan","slug":"read-markdown-s-w","errorCode":null,"errorMessage":"read Markdown [%s]: %w","messagePattern":"read Markdown \\[(.+?)\\]: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/import_obsidian.go","lineNumber":953,"sourceCode":"\tif err != nil {\n\t\treturn nil, err\n\t}\n\tafter, err := os.Stat(file.AbsPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif before.Size() != after.Size() || !before.ModTime().Equal(after.ModTime()) {\n\t\treturn nil, fmt.Errorf(\"%w while reading\", errObsidianSourceChanged)\n\t}\n\treturn data, nil\n}\n\nfunc newObsidianReadUserError(file *obsidianSourceFile, err error) error {\n\tlanguage := 345\n\tif errors.Is(err, errObsidianSourceChanged) {\n\t\tlanguage = 346\n\t}\n\treturn newObsidianUserError(language, file.RelPath, fmt.Errorf(\"read Markdown [%s]: %w\", file.RelPath, err))\n}\n\nfunc validateObsidianReadableFile(file *obsidianSourceFile) error {\n\topened, err := os.Open(file.AbsPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer opened.Close()\n\tbuffer := make([]byte, 1)\n\t_, err = opened.Read(buffer)\n\tif err != nil && !errors.Is(err, io.EOF) {\n\t\treturn err\n\t}\n\treturn validateObsidianSourceMetadata(file)\n}\n\nfunc buildObsidianHeadingIndex(doc *obsidianDocPlan, tree *parse.Tree) {\n\tstack := make([]string, 6)","sourceCodeStart":935,"sourceCodeEnd":971,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/import_obsidian.go#L935-L971","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if _, err := os.Stat(absPath); err != nil { /* file missing/unreadable before import */ }\nif info, err := os.Stat(absPath); err == nil && info.IsDir() { /* skip non-files */ }","typeGuard":null,"tryCatchPattern":"if err != nil {\n    if errors.Is(err, errObsidianSourceChanged) { /* re-run import */ }\n    if os.IsNotExist(errors.Unwrap(err)) { /* restore/resync the file */ }\n    if os.IsPermission(errors.Unwrap(err)) { /* fix permissions */ }\n}","preventionTips":["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"],"tags":["go","obsidian-import","file-io"],"backgroundTag":"file-read-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}