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
- 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
- Check for mutual nesting introduced by two different plugins each configuring the other's set
- 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
- Declare each set's parent in exactly one place (its owning plugin)
- Keep a documented diagram of the set hierarchy as it grows
- Initialize schedules in a dedicated test per plugin configuration to catch cycles at CI time
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.