toeverything/AFFiNE · error · Error
No root node found in the tree
Error message
No root node found in the tree
What it means
_rebuildBlockTree picks draftModels[0] as the root; when the flat draft list is empty, nodeMap.get(undefined) is undefined and it throws this plain Error. An empty list means the snapshot contained no convertible blocks: either snapshot.blocks was empty, or every block failed _snapshotToModel transformation — those failures are only console.error'ed ('Error when transforming snapshot to model data') and filtered out, leaving nothing to rebuild.
Source
Thrown at blocksuite/framework/store/src/transformer/transformer.ts:600
nodeMap.set(draft.id, { draft, snapshot, children: [] });
});
const root = nodeMap.get(draftModels[0].draft.id) as DraftBlockTreeNode;
// Second pass: build the tree structure
draftModels.forEach(({ draft, parentId, index }) => {
const node = nodeMap.get(draft.id);
if (!node) return;
if (parentId) {
const parentNode = nodeMap.get(parentId);
if (parentNode && index !== undefined) {
parentNode.children[index] = node;
}
}
});
if (!root) {
throw new Error('No root node found in the tree');
}
return root;
}
private async _snapshotToBlock(
snapshot: BlockSnapshot,
doc: Store,
parent?: string,
index?: number
): Promise<BlockModel | null> {
this._triggerBeforeImportEvent(snapshot, parent, index);
const flatSnapshots: FlatSnapshot[] = [];
this._flattenSnapshot(snapshot, flatSnapshots, parent, index);
const blockTree = await this._convertFlatSnapshots(flatSnapshots);
View on GitHub (pinned to b4c8548c09)
Solutions
- Check snapshot.blocks is present and non-empty before calling the import API.
- Watch the console for 'Error when transforming snapshot to model data' entries — they name the real per-block failure that emptied the list.
- Fix the block schema/props mismatch causing drafts to be dropped.
- Reject or skip empty snapshots upstream instead of passing them to the transformer.
Example fix
// before
const doc = await transformer.snapshotToDoc(collection, snapshot);
// after
if (!snapshot.blocks?.length) {
throw new Error('snapshot has no blocks to import');
}
const doc = await transformer.snapshotToDoc(collection, snapshot); Defensive patterns
Strategy: validation
Validate before calling
function countBlocks(snapshot: { blocks?: { children?: unknown[] } }): number {
return 1 + (snapshot.blocks?.children ?? []).reduce(
(n, c) => n + countBlocks(c as never), 0
);
}
if (countBlocks(snapshot) === 0) {
throw new Error('snapshot contains no blocks');
}
await transformer.snapshotToDoc(collection, snapshot); Type guard
const snapshotHasBlocks = (snapshot: DocSnapshot): boolean => Boolean(snapshot.blocks);
Try / catch
try {
const doc = await transformer.snapshotToDoc(collection, snapshot);
} catch (e) {
if (e instanceof Error && e.message === 'No root node found in the tree') {
// empty snapshot: check console for per-block transform errors, fix schema/props, retry
} else throw e;
} Prevention
- Reject empty-blocks snapshots at the API boundary before invoking the transformer.
- Watch for 'Error when transforming snapshot to model data' console errors — they explain why drafts were dropped.
- Validate snapshot shape (blocks present, children arrays well-formed) after JSON.parse.
When it happens
Trigger: Calling snapshot-to-doc with a snapshot whose blocks array is empty; importing a snapshot where every block's props transformation threw, so all drafts were dropped.
Common situations: Hand-built or truncated snapshot JSON; schema/prop mismatches (e.g. a required prop failing to convert) silently discarding all blocks; slices exported from empty selections.
Related errors
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/e73cd2b405f071ee.
Report an issue: GitHub.