{"record":{"id":"3c0e285040a6da75","repo":"siyuan-note/siyuan","slug":"obsidian-vault-has-no-readable-markdown","errorCode":null,"errorMessage":"Obsidian Vault has no readable Markdown","messagePattern":"Obsidian Vault has no readable Markdown","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/import_obsidian.go","lineNumber":234,"sourceCode":"}\n\nfunc (err *obsidianUserError) Unwrap() error {\n\treturn err.Cause\n}\n\nfunc newObsidianUserError(detailLanguage int, relPath string, cause error) error {\n\treturn &obsidianUserError{DetailLanguage: detailLanguage, RelPath: relPath, Cause: cause}\n}\n\nvar (\n\tobsidianTasksMu                 sync.Mutex\n\tobsidianTasks                   = map[string]*obsidianTask{}\n\tobsidianActive                  string\n\terrObsidianVaultUnreadable      = errors.New(\"Obsidian Vault is unreadable\")\n\terrObsidianVaultNotDirectory    = errors.New(\"Obsidian Vault path is not a directory\")\n\terrObsidianVaultUnsafePath      = errors.New(\"Obsidian Vault path is unsafe\")\n\terrObsidianVaultConfigMissing   = errors.New(\"Obsidian Vault config directory is missing\")\n\terrObsidianVaultMarkdownMissing = errors.New(\"Obsidian Vault has no readable Markdown\")\n\terrObsidianSourceChanged        = errors.New(\"Obsidian source file changed\")\n\n\tobsidianBlockIDPattern  = regexp.MustCompile(`(?m)(?:^|[ \\t])\\^([A-Za-z0-9-]+)[ \\t]*$`)\n\tobsidianQuotePattern    = regexp.MustCompile(`^((?:[ \\t]*>[ \\t]?)+)(.*)$`)\n\tobsidianListItemPattern = regexp.MustCompile(`^([ \\t]*(?:[-+*]|\\d+[.)])[ \\t]+)(.*)$`)\n\tobsidianFootnotePattern = regexp.MustCompile(`(?m)\\[\\^[^\\]\\r\\n]+\\]`)\n)\n\nfunc StartObsidianVaultAnalysis(localPath string) (*ObsidianVaultTask, error) {\n\tvar replacedTaskID string\n\tobsidianTasksMu.Lock()\n\tif obsidianActive != \"\" {\n\t\tif active := obsidianTasks[obsidianActive]; active != nil && !isObsidianTerminalState(active.State) {\n\t\t\tif !isObsidianPreImportState(active.State) {\n\t\t\t\tobsidianTasksMu.Unlock()\n\t\t\t\treturn nil, errors.New(Conf.Language(329))\n\t\t\t}\n\t\t\tif active.Cancel != nil {","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/import_obsidian.go#L216-L252","documentation":"A sentinel error returned by planObsidianDocuments when the Vault contains zero importable Markdown files after scanning. Even though the Vault root and .obsidian config are valid, if no .md files survive the scan (all skipped as hidden, special, or non-importable), planning aborts. The obsidianVaultErrorLanguage helper maps it to i18n key 340.","triggerScenarios":"startObsidianVaultAnalysis succeeds at the root-validation stage, but scanObsidianVaultFiles finds no files ending in .md with an importable mode, so len(mdFiles) == 0 in planObsidianDocuments (line 707-708). Common when all Markdown files are dotfiles, symlinked, or filtered out by isObsidianImportableFileMode.","commonSituations":"A Vault where every note is hidden (prefixed with .); all .md files are symlinks (skipped via SkippedLinkCount); the Vault only contains non-Markdown attachments; a freshly created Vault with no notes yet; files with modes isObsidianImportableFileMode rejects.","solutions":["Ensure the Vault contains at least one non-hidden .md file that is a regular file (not a symlink).","Rename dotfile notes (starting with .) so they are not skipped by the hidden-file filter.","Replace symlinked notes with real files or copy their content into the Vault directly.","Open the Vault in Obsidian and confirm notes appear in the file explorer before importing."],"exampleFix":"// before: vault only has .hidden-note.md (skipped as hidden)\n// planObsidianDocuments -> errObsidianVaultMarkdownMissing\n\n// after: add or rename a regular .md file\nmv .hidden-note.md visible-note.md","handlingStrategy":"validation","validationCode":"// Count importable Markdown files before calling analysis\nfunc countImportableMarkdown(vaultDir string) (int, error) {\n    count := 0\n    err := filepath.WalkDir(vaultDir, func(p string, d fs.DirEntry, err error) error {\n        if err != nil { return err }\n        if d.IsDir() || strings.HasPrefix(d.Name(), \".\") { return nil }\n        if strings.EqualFold(filepath.Ext(p), \".md\") { count++ }\n        return nil\n    })\n    return count, err\n}","typeGuard":null,"tryCatchPattern":"if _, err := model.StartObsidianVaultAnalysis(localPath); err != nil {\n    if errors.Is(err, errObsidianVaultMarkdownMissing) {\n        return errors.New(\"the Vault contains no importable Markdown notes; add or unhide .md files\")\n    }\n}","preventionTips":["Ensure at least one non-hidden .md file exists in the Vault.","Avoid symlinked or dotfile-only notes.","Confirm notes are visible in the Obsidian file explorer before importing."],"tags":["obsidian","import","validation","sentinel-error","markdown"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}