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

  1. Inspect the source markdown document for corruption or emptiness and repair it
  2. Log/mark the failing note and continue the import for remaining documents
  3. 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

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)