{"record":{"id":"7a30fcc4b2e4fcbb","repo":"BabylonJS/Babylon.js","slug":"context-invalid-recursive-node-hierarchy","errorCode":null,"errorMessage":"${context}: Invalid recursive node hierarchy","messagePattern":"(.+?): Invalid recursive node hierarchy","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/dev/loaders/src/glTF/2.0/glTFLoader.pure.ts","lineNumber":959,"sourceCode":"        }\r\n    }\r\n\r\n    /**\r\n     * Loads a glTF node.\r\n     * @param context The context when loading the asset\r\n     * @param node The glTF node property\r\n     * @param assign A function called synchronously after parsing the glTF properties\r\n     * @returns A promise that resolves with the loaded Babylon mesh when the load is complete\r\n     */\r\n\r\n    public loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void = () => {}): Promise<TransformNode> {\r\n        const extensionPromise = this._extensionsLoadNodeAsync(context, node, assign);\r\n        if (extensionPromise) {\r\n            return extensionPromise;\r\n        }\r\n\r\n        if (node._babylonTransformNode) {\r\n            throw new Error(`${context}: Invalid recursive node hierarchy`);\r\n        }\r\n\r\n        const promises = new Array<Promise<unknown>>();\r\n\r\n        this.logOpen(`${context} ${node.name || \"\"}`);\r\n\r\n        const loadNode = (babylonTransformNode: TransformNode) => {\r\n            GLTFLoader.AddPointerMetadata(babylonTransformNode, context);\r\n            GLTFLoader._LoadTransform(node, babylonTransformNode);\r\n\r\n            if (node.camera != undefined) {\r\n                const camera = ArrayItem.Get(`${context}/camera`, this._gltf.cameras, node.camera);\r\n                promises.push(\r\n                    this.loadCameraAsync(`/cameras/${camera.index}`, camera, (babylonCamera) => {\r\n                        babylonCamera.parent = babylonTransformNode;\r\n                        if (!this._babylonScene.useRightHandedSystem) {\r\n                            babylonTransformNode.scaling.x = -1; // Cancelling root node scaling for handedness so the view matrix does not end up flipped.\r\n                        }\r","sourceCodeStart":941,"sourceCodeEnd":977,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/loaders/src/glTF/2.0/glTFLoader.pure.ts#L941-L977","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (nodes in .gltf)\n// nodes[0].children = [1], nodes[1].children = [0]  // cycle\n// after\n// nodes[0].children = [1], nodes[1].children = []","handlingStrategy":"validation","validationCode":"// Detect cycles in the glTF node graph before loading\nfunction hasNodeCycle(gltf) {\n  const state = new Map();\n  const visit = (i) => {\n    if (state.get(i) === 1) return true;\n    if (state.get(i) === 2) return false;\n    state.set(i, 1);\n    for (const c of gltf.nodes[i].children ?? []) if (visit(c)) return true;\n    state.set(i, 2);\n    return false;\n  };\n  return gltf.nodes.some((_, i) => visit(i));\n}\nif (hasNodeCycle(gltf)) throw new Error(\"glTF has cyclic node hierarchy\");","typeGuard":"function isAcyclicNodeGraph(gltf) {\n  return !hasNodeCycle(gltf);\n}","tryCatchPattern":"try {\n  await loader.loadAsync(url);\n} catch (e) {\n  if (e.message.includes(\"Invalid recursive node hierarchy\")) {\n    console.error(\"Asset has a cyclic node graph — repair with gltf-transform before loading\");\n  } else throw e;\n}","preventionTips":["Run all glTF assets through glTF-Validator in CI","Avoid hand-editing node children arrays; use gltf-transform for reparenting"],"tags":["gltf","malformed-asset","hierarchy"],"backgroundTag":"cyclic-graph-detected","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}