siyuan-note/siyuan · error
parse tree failed
Error message
parse tree failed
What it means
During Obsidian markdown conversion the kernel parses the document with the Lute engine into a syntax tree. If `parse.Parse` returns nil the conversion cannot continue, so it fails with 'parse tree failed'. This is an internal guard against a degenerate parse result.
Solutions
- Inspect the source markdown document for corruption or emptiness and repair it
- Log/mark the failing note and continue the import for remaining documents
- Update Lute / the kernel, then retry the import
Example fix
// before: hard failure on nil tree
tree := parse.Parse("", markdown, luteEngine.ParseOptions)
if tree == nil {
return nil, errors.New("parse tree failed")
}
// after: fall back to a minimal tree so the note still imports
tree := parse.Parse("", markdown, luteEngine.ParseOptions)
if tree == nil {
stats.AddSkipped("parse failed")
return nil, stats
} Defensive patterns
Strategy: try-catch
Validate before calling
if len(bytes.TrimSpace(markdown)) == 0 {
return errors.New("empty markdown input")
} Try / catch
tree, err := convertMarkdown(markdown)
if err != nil {
log.Printf("skipping document: %v", err)
return nil
} Prevention
- Repair corrupt Obsidian notes before import
- Keep Lute and the kernel in sync
- Log failing documents and continue rather than aborting the whole import
When it happens
Trigger: Calling the conversion path (which configures callouts, sup, arbitrary task-list markers, then `parse.Parse("", markdown, luteEngine.ParseOptions)`) when the parser returns a nil tree for the given markdown bytes.
Common situations: Malformed or empty Obsidian markdown input; extremely unusual constructs that defeat the parser; a Lute engine version producing nil for edge-case input.
Related errors
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/3fcf864ac23968db.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/import_obsidian.go:2213
ret[index] = ' '
}
}
}
return ret
}
func parseObsidianMd(markdown []byte, vault *obsidianVaultContext, doc *obsidianDocPlan, stats *obsidianTransformStats) (*parse.Tree, error) {
luteEngine := util.NewStdLute()
luteEngine.SetYamlFrontMatter(true)
luteEngine.SetBlockRef(true)
luteEngine.SetKramdownIAL(true)
luteEngine.SetCallout(true)
luteEngine.SetArbitraryTaskListItemMarker(true)
luteEngine.SetExportNormalizeTaskListMarker(false)
luteEngine.SetSup(true)
tree := parse.Parse("", markdown, luteEngine.ParseOptions)
if tree == nil {
return nil, errors.New("parse tree failed")
}
normalizeTree(tree)
normalizeObsidianCallouts(tree)
htmlBlock2Inline(tree)
parse.TextMarks2Inlines(tree)
parse.NestedInlines2FlattedSpansHybrid(tree, false)
markObsidianFootnoteRefs(tree, stats)
rewriteObsidianMarkdownLinks(tree, vault, doc, stats)
assignObsidianTreeIDs(tree, doc, stats.ReservedIDs)
return tree, nil
}
func markObsidianFootnoteRefs(tree *parse.Tree, stats *obsidianTransformStats) {
if len(stats.FootnoteIDs) == 0 {
return
}
ast.Walk(tree.Root, func(node *ast.Node, entering bool) ast.WalkStatus {
if entering && treenode.IsBlockRef(node) && stats.FootnoteIDs[node.TextMarkBlockRefID] {View on GitHub (pinned to 9f775e8a12)