siyuan-note/siyuan · error · obsidianUserError
347
347
Error message
parse Markdown [%s] failed
What it means
First analysis pass: parseStdMd(data) returned a nil parse.Tree for a markdown file. Reported with language code 347 ('Unable to convert Markdown file [%s]'). parseStdMd parses the file as standard (non-SiYuan) Markdown via Lute; a nil tree means Lute rejected the input outright rather than producing a partial AST. This blocks heading/block-ID indexing for that file.
Source
Thrown at kernel/model/import_obsidian.go:876
return newObsidianReadUserError(doc.Source, err)
}
if !utf8.Valid(data) {
return newObsidianUserError(344, doc.Source.RelPath,
fmt.Errorf("Markdown [%s] is not valid UTF-8", doc.Source.RelPath))
}
scan := scanObsidianSource(data)
for _, blockID := range scan.BlockIDs {
if scan.Duplicates[blockID] {
doc.DuplicateBlocks[blockID] = true
}
if doc.BlockIDs[blockID] == "" {
doc.BlockIDs[blockID] = ast.NewNodeID()
}
}
tree, _, _, _ := parseStdMd(data)
if tree == nil {
return newObsidianUserError(347, doc.Source.RelPath,
fmt.Errorf("parse Markdown [%s] failed", doc.Source.RelPath))
}
buildObsidianHeadingIndex(doc, tree)
vault.Analysis.WikiLinkCount += countObsidianNonEmbedTokens(scan.Wikis)
vault.Analysis.EmbedCount += countObsidianEmbedTokens(scan.Wikis)
vault.Analysis.CommentCount += len(scan.Comments)
vault.Analysis.FootnoteCount += scan.Footnotes
vault.Analysis.BlockIDCount += len(scan.BlockIDs)
processed++
progress(35+processed*30/maxInt(sourceCount, 1), "Analyzing Markdown syntax")
}
for _, doc := range vault.Docs {
if doc.Synthetic {
continue
}
if err := ctx.Err(); err != nil {
return err
}View on GitHub (pinned to 251596fc0d)
Solutions
- Open the reported file in a Markdown linter/editor and fix or simplify the offending construct (unclosed code fences, runaway nesting).
- If the file is not real Markdown, rename it to a non-.md extension or move it out of the vault.
- Trim/truncate an absurdly large single file and retry analysis; if it then parses, narrow down the offending region.
- Check the kernel log for the underlying Lute error/stack to identify the exact construct, then file a Lute issue with a minimal reproducer.
Example fix
// before: file with an unterminated code fence spanning the whole doc // ```python // ... 10MB of content with no closing fence // after: close the fence (or split the file) // ```python // ... // ```
Defensive patterns
Strategy: validation
Validate before calling
// Pre-parse each markdown with a lenient parser/linter to catch hard failures.
func vaultMarkdownParses(root string) (string, error) {
var first string
err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || !strings.HasSuffix(p, ".md") { return err }
b, e := os.ReadFile(p); if e != nil { return e }
if _, e := parseStdMd(b); e != nil || treeIsNil { first = p; return errors.New("unparseable") }
return nil
})
return first, err
} Type guard
null
Try / catch
null
Prevention
- Lint vault markdown (markdownlint / mdformat) before import.
- Close all code fences and blockquotes; avoid runaway nesting.
- Keep files to reasonable sizes to avoid parser edge cases.
When it happens
Trigger: Severe Markdown corruption that defeats the parser: file containing mostly binary/null bytes that nonetheless passed UTF-8 validation, extremely deep nesting exceeding parser limits, or a Lute internal panic recovered into a nil tree. Rare in practice because most malformed Markdown still yields a permissive AST.
Common situations: A .md file that is UTF-8 but structurally pathological (megabytes of unclosed brackets/fences, deeply nested blockquote/list stacks); a file that is actually a different format renamed to .md; a Lute version regression that fails on a specific construct.
Related errors
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/eaba7be4bdcac313.
Report an issue: GitHub.