BabylonJS/Babylon.js · error · Error
${context}: Invalid recursive node hierarchy
Error message
${context}: Invalid recursive node hierarchy What it means
While loading a glTF node hierarchy, the loader detects that a node's _babylonTransformNode is already assigned when _loadNodeAsync is entered again, meaning the node appears in its own ancestor chain. This indicates a cycle in the glTF 'nodes' children arrays, which cannot be represented as a TransformNode tree, so the loader throws.
Source
Thrown at packages/dev/loaders/src/glTF/2.0/glTFLoader.pure.ts:959
}
}
/**
* Loads a glTF node.
* @param context The context when loading the asset
* @param node The glTF node property
* @param assign A function called synchronously after parsing the glTF properties
* @returns A promise that resolves with the loaded Babylon mesh when the load is complete
*/
public loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void = () => {}): Promise<TransformNode> {
const extensionPromise = this._extensionsLoadNodeAsync(context, node, assign);
if (extensionPromise) {
return extensionPromise;
}
if (node._babylonTransformNode) {
throw new Error(`${context}: Invalid recursive node hierarchy`);
}
const promises = new Array<Promise<unknown>>();
this.logOpen(`${context} ${node.name || ""}`);
const loadNode = (babylonTransformNode: TransformNode) => {
GLTFLoader.AddPointerMetadata(babylonTransformNode, context);
GLTFLoader._LoadTransform(node, babylonTransformNode);
if (node.camera != undefined) {
const camera = ArrayItem.Get(`${context}/camera`, this._gltf.cameras, node.camera);
promises.push(
this.loadCameraAsync(`/cameras/${camera.index}`, camera, (babylonCamera) => {
babylonCamera.parent = babylonTransformNode;
if (!this._babylonScene.useRightHandedSystem) {
babylonTransformNode.scaling.x = -1; // Cancelling root node scaling for handedness so the view matrix does not end up flipped.
}
View on GitHub (pinned to 0592b347b8)
Solutions
- Fix the source glTF so the node graph is a tree (remove the cyclic children reference)
- Run the asset through glTF-Validator or a tool like gltf-transform to detect and report the cycle
- Re-export the model from the original DCC tool with correct parenting
- If assets are generated in code, ensure each node is added as a child at most once and never to its own descendant
Example fix
// before (nodes in .gltf) // nodes[0].children = [1], nodes[1].children = [0] // cycle // after // nodes[0].children = [1], nodes[1].children = []
Defensive patterns
Strategy: validation
Validate before calling
// Detect cycles in the glTF node graph before loading
function hasNodeCycle(gltf) {
const state = new Map();
const visit = (i) => {
if (state.get(i) === 1) return true;
if (state.get(i) === 2) return false;
state.set(i, 1);
for (const c of gltf.nodes[i].children ?? []) if (visit(c)) return true;
state.set(i, 2);
return false;
};
return gltf.nodes.some((_, i) => visit(i));
}
if (hasNodeCycle(gltf)) throw new Error("glTF has cyclic node hierarchy"); Type guard
function isAcyclicNodeGraph(gltf) {
return !hasNodeCycle(gltf);
} Try / catch
try {
await loader.loadAsync(url);
} catch (e) {
if (e.message.includes("Invalid recursive node hierarchy")) {
console.error("Asset has a cyclic node graph — repair with gltf-transform before loading");
} else throw e;
} Prevention
- Run all glTF assets through glTF-Validator in CI
- Avoid hand-editing node children arrays; use gltf-transform for reparenting
When it happens
Trigger: Loading a malformed .gltf/.glb whose node children graph contains a cycle (node A is a child of B and B a descendant of A), typically from hand-edited JSON or a buggy exporter.
Common situations: Hand-written or programmatically generated glTF with cyclic children references; exporter bugs that emit parent/child links both ways; asset-processing scripts that reparent nodes incorrectly.
Related errors
- ${context}: Primitives are missing
- ${context}: Attributes are missing
- ${context}: Primitives do not have the same number of target
- ${context}: Camera perspective properties are missing
- Buffer access is out of range
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/7a30fcc4b2e4fcbb.
Report an issue: GitHub.