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

  1. Fix the source glTF so the node graph is a tree (remove the cyclic children reference)
  2. Run the asset through glTF-Validator or a tool like gltf-transform to detect and report the cycle
  3. Re-export the model from the original DCC tool with correct parenting
  4. 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

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


AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30). Data as JSON: /api/errors/7a30fcc4b2e4fcbb. Report an issue: GitHub.