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

  1. Restore the affected document from SiYuan history (文档历史) or from your sync source.
  2. Inspect the .sy file under data/<notebook>/<path>.sy for truncation/corruption; replace it with a valid copy.
  3. Avoid hand-editing .sy files; regenerate documents through the API instead.
  4. 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

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


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)