{"record":{"id":"cc6b1b8aea645e06","repo":"siyuan-note/siyuan","slug":"parse-tree-failed","errorCode":null,"errorMessage":"parse tree failed","messagePattern":"parse tree failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/block_update.go","lineNumber":220,"sourceCode":"\nfunc parseBlockUpdateData(data, dataType string, luteEngine *lute.Lute) (ret string, tree *parse.Tree, err error) {\n\tret = data\n\tswitch dataType {\n\tcase \"markdown\":\n\t\tret, err = DataBlockDOM(data, luteEngine)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"data block DOM failed: %w\", err)\n\t\t\treturn\n\t\t}\n\tcase \"dom\":\n\tdefault:\n\t\terr = fmt.Errorf(\"unsupported block data type [%s]\", dataType)\n\t\treturn\n\t}\n\n\ttree = luteEngine.BlockDOM2Tree(ret)\n\tif nil == tree || nil == tree.Root || nil == firstContentBlock(tree.Root) {\n\t\terr = errors.New(\"parse tree failed\")\n\t}\n\treturn\n}\n\nfunc normalizeBlockUpdateTree(oldNode *ast.Node, tree *parse.Tree, luteEngine *lute.Lute) (ret *parse.Tree, updatedNode *ast.Node, err error) {\n\tupdatedNode, err = resolveBlockUpdateNode(oldNode, tree.Root)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tupdatedNode.Unlink()\n\troot := &ast.Node{Type: ast.NodeDocument}\n\troot.AppendChild(updatedNode)\n\tret = &parse.Tree{\n\t\tRoot:    root,\n\t\tContext: &parse.Context{ParseOption: luteEngine.ParseOptions},\n\t}\n\treturn","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/block_update.go#L202-L238","documentation":"After converting the payload to block DOM and parsing it into an AST (BlockDOM2Tree), parseBlockUpdateData verifies the tree exists and contains at least one content block (firstContentBlock). If the tree is nil, its root is nil, or there is no content block, the input produced an empty/unusable parse result and this error is returned.","triggerScenarios":"Calling the block update API with data that converts to a tree with no content blocks — e.g. an empty string, whitespace-only content, or DOM containing only non-content elements. Also returned with dataType \"dom\" when the DOM string is blank or structurally empty.","commonSituations":"Scripts sending empty update payloads; race conditions where the block content was already cleared before the update call; clients constructing DOM strings with no renderable block node.","solutions":["Ensure the data payload contains at least one content block (a paragraph, heading, etc.)","Check that the DOM/markdown string is not empty or whitespace-only before calling the API","If clearing a block is intended, delete the block instead of updating it with empty content"],"exampleFix":"// before\nawait updateBlock({id, dataType: \"dom\", data: \"\"});\n// after\nawait updateBlock({id, dataType: \"dom\", data: '<div data-node-id=\"...\" data-type=\"NodeParagraph\" class=\"p\"><div class=\"p\" contenteditable=\"true\">new text</div></div>'});","handlingStrategy":"validation","validationCode":"if (!payload.data || !payload.data.trim()) throw new Error(\"block update data must not be empty\");","typeGuard":null,"tryCatchPattern":"try {\n  await updateBlock(payload);\n} catch (e) {\n  if (String(e.msg).includes(\"parse tree failed\")) {\n    // payload produced no content block; supply non-empty content\n  }\n}","preventionTips":["Never send empty or whitespace-only update payloads","Delete blocks instead of updating them with empty content","Sanity-check payloads after client-side transformations"],"tags":["parse","empty-input","dom","api"],"backgroundTag":"empty-required-field","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}