siyuan-note/siyuan · error
cannot stage an empty tree
Error message
cannot stage an empty tree
What it means
Before writing a converted document tree into the temporary staging directory, writeObsidianTempTree rejects a nil tree or a tree without a Root node. Such a tree could not be serialized into a valid .sy document, so staging fails fast with this error.
Source
Thrown at kernel/model/import_obsidian.go:2339
node.SetIALAttr("updated", util.TimeFromID(node.ID))
return ast.WalkContinue
})
tree.ID = doc.ID
tree.Box = ""
tree.Path = doc.TargetPath
tree.HPath = doc.HPath
tree.Root.Box = ""
tree.Root.Path = doc.TargetPath
tree.Root.Spec = treenode.CurrentSpec
tree.Root.SetIALAttr("id", doc.ID)
tree.Root.SetIALAttr("title", doc.Title)
tree.Root.SetIALAttr("updated", util.TimeFromID(doc.ID))
tree.Root.RemoveIALAttrsByPrefix("custom-")
}
func writeObsidianTempTree(docsTemp string, tree *parse.Tree) error {
if tree == nil || tree.Root == nil {
return errors.New("cannot stage an empty tree")
}
if tree.Root.FirstChild == nil {
tree.Root.AppendChild(treenode.NewParagraph(""))
}
treenode.UpgradeSpec(tree)
tree.Root.SetIALAttr("type", "doc")
luteEngine := util.NewLute()
renderer := render.NewJSONRenderer(tree, luteEngine.RenderOptions, luteEngine.ParseOptions)
data := renderer.Render()
if !json.Valid(data) {
return fmt.Errorf("generated tree [%s] is not valid JSON", tree.HPath)
}
if !util.UseSingleLineSave {
var buffer bytes.Buffer
if err := json.Indent(&buffer, data, "", "\t"); err != nil {
return err
}
data = buffer.Bytes()View on GitHub (pinned to 8641553a1f)
Solutions
- Fix the upstream transform so a nil tree surfaces as a proper parse error (error 347) instead of being passed to writeObsidianTempTree
- Check why the tree for that document has no Root — usually a parse.Parse nil result that was swallowed
- Re-run the import after fixing the problematic source note
- Add an explicit nil-tree check in the caller to report the document name before staging
Example fix
// before: nil tree silently passed on
tree, stats := transformObsidianMarkdown(vault, doc, data)
err := writeObsidianTempTree(docsTemp, tree)
// after: fail fast with document context
if tree == nil || tree.Root == nil {
return fmt.Errorf("transform produced no tree for doc [%s]", doc.ID)
}
err := writeObsidianTempTree(docsTemp, tree) Defensive patterns
Strategy: validation
Validate before calling
if tree == nil || tree.Root == nil {
return fmt.Errorf("transform produced no tree for doc [%s]", doc.ID)
} Type guard
func hasUsableRoot(t *parse.Tree) bool { return t != nil && t.Root != nil } Try / catch
if err := writeObsidianTempTree(docsTemp, tree); err != nil && strings.Contains(err.Error(), "empty tree") {
// find the upstream parse/transform failure that returned a nil tree
} Prevention
- Always check the transform step's error before using its tree
- Fail fast with document context instead of passing nil trees downstream
- Never swallow nil-tree returns from parse.Parse
When it happens
Trigger: Calling writeObsidianTempTree(docsTemp, tree) where the transform step previously returned nil (parse failure propagated as a nil *parse.Tree) or produced a Tree whose Root field is nil.
Common situations: A parse failure earlier in the pipeline was ignored (nil tree returned without error), leading to this downstream error instead of the original parse failure; a code path constructing a Tree literal forgot to assign Root.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Conf.Language(331)
- Conf.Language(332)
- read referenced attachment [%s]: %w
- %w: path is empty
- %w: normalize Vault path: %v
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/21a9c7e085614346.
Report an issue: GitHub.