bevyengine/bevy · error · GltfError

GLTF model must be a tree, found cycle instead at node indic

Error message

GLTF model must be a tree, found cycle instead at node indices: {0:?}

What it means

check_is_part_of_cycle (crates/bevy_gltf/src/loader/gltf_ext/scene.rs:47-68) walks the node graph with a FixedBitSet; if any node index is revisited while still on the current DFS path, it returns CircularChildren("glTF nodes form a cycle: a -> b -> ... -> n"). Bevy requires the glTF node hierarchy to be a tree, matching the spec's rule that nodes must not be their own descendants.

Source

Thrown at crates/bevy_gltf/src/loader/mod.rs:134

    InvalidImageUri(String, ParseAssetPathError),
    /// Failed to read bytes from an asset path.
    #[error("failed to read bytes from an asset path: {0}")]
    ReadAssetBytesError(#[from] ReadAssetBytesError),
    /// Failed to load asset from an asset path.
    #[error("failed to load asset from an asset path: {0}")]
    AssetLoadError(#[from] AssetLoadError),
    /// Missing sampler for an animation.
    #[error("Missing sampler for animation {0}")]
    #[from(ignore)]
    MissingAnimationSampler(usize),
    /// Failed to generate tangents.
    #[error("failed to generate tangents: {0}")]
    GenerateTangentsError(#[from] bevy_mesh::GenerateTangentsError),
    /// Failed to generate morph targets.
    #[error("failed to generate morph targets: {0}")]
    MorphTarget(#[from] bevy_mesh::morph::MorphBuildError),
    /// Circular children in Nodes
    #[error("GLTF model must be a tree, found cycle instead at node indices: {0:?}")]
    #[from(ignore)]
    CircularChildren(String),
    /// Failed to load a file.
    #[error("failed to load file: {0}")]
    Io(#[from] Error),
}

/// Loads glTF files with all of their data as their corresponding bevy representations.
#[derive(TypePath)]
pub struct GltfLoader {
    /// List of compressed image formats handled by the loader.
    pub supported_compressed_formats: CompressedImageFormats,
    /// Custom vertex attributes that will be recognized when loading a glTF file.
    ///
    /// Keys must be the attribute names as found in the glTF data, which must start with an underscore.
    /// See [this section of the glTF specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)
    /// for additional details on custom attributes.
    pub custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,

View on GitHub (pinned to 396ca72708)

Solutions

  1. Run gltf-validator — it reports node cycles explicitly.
  2. Fix the hierarchy in a DCC: parent-child links must be strictly acyclic; shared nodes must be duplicated, not cross-linked.
  3. If the DAG sharing was intentional, instantiate the shared subtree as separate root-level nodes instead.

Example fix

// (before) cycle: node0 -> node1, node1 -> node0
"nodes": [
  { "children": [1] },
  { "children": [0] }
]
// (after) tree: node1 parented under node0 only
"nodes": [
  { "children": [1] },
  {}
]
Defensive patterns

Strategy: validation

Validate before calling

fn node_graph_is_tree(doc: &gltf::Document) -> bool {
    fn visit(node: &gltf::scene::Node, seen: &mut std::collections::HashSet<usize>) -> bool {
        if !seen.insert(node.index()) { return false; }
        let ok = node.children().all(|c| visit(&c, seen));
        seen.remove(&node.index());
        ok
    }
    doc.nodes().all(|n| {
        let mut seen = std::collections::HashSet::new();
        seen.insert(n.index());
        n.children().all(|c| visit(&c, &mut seen))
    })
}

Try / catch

match err {
    GltfError::CircularChildren(chain) => {
        error!("node hierarchy has a cycle: {chain}; fix parenting in the source file");
    }
    other => return Err(other.into()),
}

Prevention

When it happens

Trigger: Node A lists B in children while B (or its descendant) lists A; a node listing itself as a child; hand-edited or programmatically generated scene graphs with bidirectional parenting.

Common situations: Manual JSON surgery to 'share' children between parents, scene-graph exporters that encode DAGs (shared instances) as cycles, corrupted files.

Related errors


AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20). Data as JSON: /api/errors/3fe31e5eec2ac075. Report an issue: GitHub.