bevyengine/bevy · critical

BuilderSystem {} was not initialized before calling run_unsa

Error message

BuilderSystem {} was not initialized before calling run_unsafe.

What it means

Systems assembled with the SystemBuilder API transition from Uninitialized to Initialized when System::initialize runs (which schedules do on first execution). This panic fires when run_unsafe is called on a BuilderSystem that is still Uninitialized - its inner system, params, and pointers were never set up against a World.

Source

Thrown at crates/bevy_ecs/src/system/builder.rs:390

        match &self.inner {
            BuilderSystemInner::Initialized { system, .. } => system.flags(),
            BuilderSystemInner::Uninitialized { meta, .. } => meta.flags(),
            BuilderSystemInner::Invalid => unreachable!(),
        }
    }

    #[inline]
    unsafe fn run_unsafe(
        &mut self,
        input: super::SystemIn<'_, Self>,
        world: UnsafeWorldCell,
    ) -> Result<Self::Out, RunSystemError> {
        match &mut self.inner {
            // SAFETY: requirements upheld by the caller.
            BuilderSystemInner::Initialized { system, .. } => unsafe {
                system.run_unsafe(input, world)
            },
            BuilderSystemInner::Uninitialized { .. } => panic!(
                "BuilderSystem {} was not initialized before calling run_unsafe.",
                self.name()
            ),
            BuilderSystemInner::Invalid => unreachable!(),
        }
    }

    #[cfg(feature = "hotpatching")]
    #[inline]
    fn refresh_hotpatch(&mut self) {
        match &mut self.inner {
            BuilderSystemInner::Initialized { system, .. } => system.refresh_hotpatch(),
            BuilderSystemInner::Uninitialized { .. } => {}
            BuilderSystemInner::Invalid => unreachable!(),
        }
    }

    #[inline]

View on GitHub (pinned to 396ca72708)

Solutions

  1. Call system.initialize(&mut world) once before the first run_unsafe - the built-in schedules do this for you.
  2. Prefer safe entry points (world.run_system_once, app.add_systems) over manual run_unsafe.
  3. When writing an executor, mirror bevy's own executors: initialize all systems during schedule initialization, then run them.

Example fix

// before
let mut system = SystemBuilder::<()>::new(&mut world).build(my_fn);
unsafe { system.run_unsafe((), world.into()) }; // panic: Uninitialized

// after
let mut system = SystemBuilder::<()>::new(&mut world).build(my_fn);
system.initialize(&mut world);
unsafe { system.run_unsafe((), world.into()) };
Defensive patterns

Strategy: validation

Validate before calling

// WithInputFromWrapper exposes its state before running:
if wrapper.value().is_none() {
    wrapper.initialize(&mut world);
}
// generally: initialize every system once before any run_unsafe

Prevention

When it happens

Trigger: Manually invoking run_unsafe on a builder-built system without a prior initialize call: custom executors running BoxedSystems directly, test helpers executing systems outside a schedule, or middleware that wraps and runs systems while bypassing initialization.

Common situations: Custom executors that skip the initialize step; systems built with SystemBuilder cached in resources and run manually; porting code from function systems (which tolerate some misuse) to the builder API.

Related errors


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