{"record":{"id":"15cfa4acb763233d","repo":"bevyengine/bevy","slug":"self-loop-detected-at-node-0","errorCode":null,"errorMessage":"self-loop detected at node `{0:?}`","messagePattern":"self-loop detected at node `(.+?)`","errorType":"exception","errorClass":"DiGraphToposortError","httpStatus":null,"severity":"error","filePath":"crates/bevy_ecs/src/schedule/graph/graph_map.rs","lineNumber":523,"sourceCode":"\n            // divide remainder into smaller SCCs\n            sccs.extend(subgraph.iter_sccs().filter(|scc| scc.len() > 1));\n        }\n\n        cycles\n    }\n\n    /// Iterate over all *Strongly Connected Components* in this graph.\n    pub(crate) fn iter_sccs(&self) -> impl Iterator<Item = SmallVec<[N; 4]>> + '_ {\n        super::tarjan_scc::new_tarjan_scc(self)\n    }\n}\n\n/// Error returned when topologically sorting a directed graph fails.\n#[derive(Error, Debug)]\npub enum DiGraphToposortError<N: GraphNodeId> {\n    /// A self-loop was detected.\n    #[error(\"self-loop detected at node `{0:?}`\")]\n    Loop(N),\n    /// Cycles were detected.\n    #[error(\"cycles detected: {0:?}\")]\n    Cycle(Vec<Vec<N>>),\n}\n\n/// Edge direction.\n#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]\n#[repr(u8)]\npub enum Direction {\n    /// An `Outgoing` edge is an outward edge *from* the current node.\n    Outgoing = 0,\n    /// An `Incoming` edge is an inbound edge *to* the current node.\n    Incoming = 1,\n}\n\nimpl Direction {\n    /// Return the opposite `Direction`.","sourceCodeStart":505,"sourceCodeEnd":541,"githubUrl":"https://github.com/bevyengine/bevy/blob/396ca727080776bd313bb892423b7d94e03b81b4/crates/bevy_ecs/src/schedule/graph/graph_map.rs#L505-L541","documentation":"Bevy's schedule builder turns ordering constraints (.before/.after/.chain) and set membership into edges of a directed graph, then topologically sorts it to pick a run order. This error (DiGraphToposortError::Loop) means a single node - a system or system set - has an edge pointing directly at itself, so no valid ordering exists. It is produced while the schedule graph is topologically sorted during schedule initialization.","triggerScenarios":"An ordering or membership constraint that resolves to the node itself: system fn ordered against itself (move_player.before(move_player) resolves the function name to that same system node), a set configured into itself (Physics.in_set(Physics)), or generated/config-driven code that emits a constraint whose source and target are the same node.","commonSituations":"Copy-pasted ordering constraints where the target name was not updated; sets accidentally nested into themselves during refactors; macro-generated plugin code that applies a user-supplied ordering twice onto the same node; Bevy upgrades where trivially self-referential ordering stopped being silently ignored.","solutions":["Map the node id printed in the error back to the system/set it names, then inspect its .before()/.after()/.in_set() constraints and delete the one that points the node at itself.","If two different systems were intended, order against a distinct function or wrap the target in a named SystemSet and order against that set.","Add a startup test that calls app.update() once (or schedule.initialize(&mut world)) so graph errors fail in CI instead of at first frame."],"exampleFix":"// before\napp.configure_sets(Update, Physics.in_set(Physics)); // set is a member of itself -> self-loop\n\n// after\napp.configure_sets(Update, Physics);","handlingStrategy":"validation","validationCode":"// Validate the schedule graph at startup instead of first run\n#[test]\nfn schedule_graph_is_valid() {\n    let mut app = App::new();\n    // register systems/sets exactly as the real app does\n    app.update(); // panics (with the offending node named) if a self-loop exists\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = schedule.initialize(&mut world) {\n    // formatted against the graph: names the self-looped node\n    log::error!(\"schedule build failed: {}\", e.to_string(schedule.graph(), world));\n}","preventionTips":["Prefer tuple .chain() over hand-written .before()/.after() - it cannot self-reference.","Keep ordering constraints in one place (configure_sets) instead of repeating them per system.","Run one app.update() smoke test per plugin so graph errors surface in CI."],"tags":["bevy","ecs","schedule","system-ordering","dependency-cycle","topological-sort"],"backgroundTag":"circular-dependency-detected","analyzedSha":"396ca727080776bd313bb892423b7d94e03b81b4","analyzedAt":"2026-08-20T16:12:39.808Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}