siyuan-note/siyuan · critical
load block tree [ ] failed: tree is empty
Error message
load block tree [%s] failed: tree is empty
What it means
After loadTree succeeds, buildBlockUpdateOperations defensively checks that the parsed tree and its root are non-nil; otherwise it returns "load block tree [%s] failed: tree is empty". This indicates the document loaded but produced an unusable AST — a corrupt or empty .sy file.
Solutions
- Restore the affected document from SiYuan history (文档历史) or from your sync source.
- Inspect the .sy file under data/<notebook>/<path>.sy for truncation/corruption; replace it with a valid copy.
- Avoid hand-editing .sy files; regenerate documents through the API instead.
- If sync-related, re-sync the notebook from a known-good peer before retrying the update.
Defensive patterns
Strategy: fallback
Validate before calling
async function docHealthy(rootId) {
const res = await fetchPost('/api/export/preview', { id: rootId }); // or getDoc
return res.code === 0 && res.data && res.data.content && res.data.content.length > 0;
} Try / catch
try { await updateBlocks(inputs); } catch (e) { if (String(e.msg).includes('tree is empty')) { console.error('document file corrupt; restore from history/sync before editing'); } throw e; } Prevention
- Never hand-edit .sy files under data/.
- Ensure clean shutdowns and verify sync snapshots complete before editing.
- Keep document history/snapshots enabled so corrupt files are recoverable.
When it happens
Trigger: Updating a block inside a .sy file that parses to an empty tree: zero-byte or truncated document file (interrupted write/sync), or corrupted JSON content that parses without error but yields no root.
Common situations: Power loss or crash mid-write leaving a truncated .sy file; sync engine materializing an incomplete snapshot; manual edits to data/*.sy breaking the JSON structure.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- block not found [ ]
- block [ ] is not a document
- block [ ] is not a document that can declare a child…
- block [ ] not found
- block updates are empty
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/49789f7704eae302.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/block_update.go:116
return nil, nil, parseErr
}
var oldTree *parse.Tree
var cacheKey blockUpdateTreeKey
hasCacheKey := false
if blockTree := resolveTree(input.ID); nil != blockTree {
cacheKey = blockUpdateTreeKey{boxID: blockTree.BoxID, rootID: blockTree.RootID}
hasCacheKey = true
oldTree = treeCache[cacheKey]
}
if nil == oldTree {
var loadErr error
oldTree, loadErr = loadTree(input.ID)
if loadErr != nil {
return nil, nil, fmt.Errorf("load block tree [%s] failed: %w", input.ID, loadErr)
}
if nil == oldTree || nil == oldTree.Root {
return nil, nil, fmt.Errorf("load block tree [%s] failed: tree is empty", input.ID)
}
treeCache[blockUpdateTreeKey{boxID: oldTree.Box, rootID: oldTree.ID}] = oldTree
if hasCacheKey {
treeCache[cacheKey] = oldTree
}
}
oldNode := treenode.GetNodeInTree(oldTree, input.ID)
if nil == oldNode {
return nil, nil, fmt.Errorf("block [%s] not found", input.ID)
}
if _, ok := rootIDSet[oldTree.ID]; !ok {
rootIDSet[oldTree.ID] = struct{}{}
rootIDs = append(rootIDs, oldTree.ID)
}
if ast.NodeDocument == oldNode.Type {
if validateErr := treenode.ValidateBlockSubtree(dataTree.Root); validateErr != nil {View on GitHub (pinned to 9f775e8a12)