bevyengine/bevy · error · ScheduleBuildError

HierarchySort

HierarchySort

Error message

Failed to topologically sort the hierarchy of system sets: {0}

What it means

During `Schedule::initialize`, Bevy topologically sorts the system-set hierarchy (the `in_set` containment graph). `HierarchySort` reports that this graph has a cycle: set containment is circular (directly or through a chain), so no valid nesting order exists. The wrapped `DiGraphToposortError<NodeId>` names the node where the cycle was detected.

Source

Thrown at crates/bevy_ecs/src/schedule/error.rs:24

use crate::{
    component::Components,
    schedule::{
        graph::{
            DagCrossDependencyError, DagOverlappingGroupError, DagRedundancyError,
            DiGraphToposortError, GraphNodeId,
        },
        AmbiguousSystemConflictsWarning, ConflictingSystems, NodeId, ScheduleGraph, SystemKey,
        SystemSetKey, SystemTypeSetAmbiguityError,
    },
    world::World,
};

/// Category of errors encountered during [`Schedule::initialize`](crate::schedule::Schedule::initialize).
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum ScheduleBuildError {
    /// Tried to topologically sort the hierarchy of system sets.
    #[error("Failed to topologically sort the hierarchy of system sets: {0}")]
    HierarchySort(DiGraphToposortError<NodeId>),
    /// Tried to topologically sort the dependency graph.
    #[error("Failed to topologically sort the dependency graph: {0}")]
    DependencySort(DiGraphToposortError<NodeId>),
    /// Tried to topologically sort the flattened dependency graph.
    #[error("Failed to topologically sort the flattened dependency graph: {0}")]
    FlatDependencySort(DiGraphToposortError<SystemKey>),
    /// Tried to order a system (set) relative to a system set it belongs to.
    #[error("`{:?}` and `{:?}` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.", .0.0, .0.1)]
    CrossDependency(#[from] DagCrossDependencyError<NodeId>),
    /// Tried to order system sets that share systems.
    #[error("`{:?}` and `{:?}` have a `before`-`after` relationship (which may be transitive) but share systems.", .0.0, .0.1)]
    SetsHaveOrderButIntersect(#[from] DagOverlappingGroupError<SystemSetKey>),
    /// Tried to order a system (set) relative to all instances of some system function.
    #[error(transparent)]
    SystemTypeSetAmbiguity(#[from] SystemTypeSetAmbiguityError),
    /// Tried to run a schedule before all of its systems have been initialized.
    #[error("Tried to run a schedule before all of its systems have been initialized.")]

View on GitHub (pinned to 396ca72708)

Solutions

  1. Read the node ids in the error and map them back to sets (the error's formatting includes the failing node), then delete one `.in_set` edge to break the loop
  2. Check for mutual nesting introduced by two different plugins each configuring the other's set
  3. Reproduce the schedule in a small test and remove edges until it initializes to confirm the cycle is gone

Example fix

// before
app.configure_sets(Update, (SetA.in_set(SetB), SetB.in_set(SetA))); // cycle

// after
app.configure_sets(Update, (SetA.in_set(SetB),));
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate set-hierarchy acyclicity cheaply before adding a nesting edge
// (maintain your own adjacency for plugin-owned sets, or rely on initialize below)
let result = schedule.initialize(&mut world);
match result {
    Ok(_) => {}
    Err(e) => tracing::error!(%e, "schedule build failed"),
}

Try / catch

match schedule.initialize(&mut world) {
    Ok(_) => { /* proceed */ }
    Err(ScheduleBuildError::HierarchySort(err)) => {
        // err carries the NodeId where the cycle was detected; fix the .in_set edges
    }
    Err(other) => return Err(other.into()),
}

Prevention

When it happens

Trigger: `.configure_sets(Schedule, A.in_set(B))` combined (directly or transitively) with `B.in_set(A)`; a set configured into itself (`A.in_set(A)`); two plugins that each nest their set inside the other's.

Common situations: Merging plugins that mutually reference each other's sets; refactoring set hierarchies and leaving an old `.in_set` edge behind; copy-pasted configure_sets calls that reverse the intended nesting.

Related errors


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