bevyengine/bevy · error

The TimeSender channel should always be empty during render.

Error message

The TimeSender channel should always be empty during render. You might need to add the bevy::core::time_system to your app.

What it means

The render app's `send_time` system pushes `Instant::now()` to the main world through the capacity-1 `TimeSender` channel each frame; the main world's `time_system` drains it between frames. If the channel is still full when the render app runs, `try_send` fails with `TrySendError::Full` and the system panics: the receiving time pipeline never ran, typically because `TimePlugin`/bevy's `time_system` was removed or a custom runner skips it.

Source

Thrown at crates/bevy_render/src/lib.rs:527

fn renderer_is_ready(state: Res<RenderState>) -> bool {
    matches!(*state, RenderState::Ready)
}

fn run_render_schedule(world: &mut World) {
    world.resource_scope(|world, order: Mut<RenderScheduleOrder>| {
        for &label in &order.labels {
            let _ = world.try_run_schedule(label);
        }
    });
}

fn send_time(time_sender: Res<TimeSender>) {
    // update the time and send it to the app world regardless of whether we render
    if let Err(error) = time_sender.0.try_send(Instant::now()) {
        match error {
            bevy_time::TrySendError::Full(_) => {
                panic!(
                    "The TimeSender channel should always be empty during render. \
                            You might need to add the bevy::core::time_system to your app."
                );
            }
            bevy_time::TrySendError::Disconnected(_) => {
                // ignore disconnected errors, the main world probably just got dropped during shutdown
            }
        }
    }
}

/// Inserts a [`FutureRenderResources`] created from this [`RenderCreation`].
///
/// Returns true if creation was successful, false otherwise.
fn insert_future_resources(render_creation: &RenderCreation, main_world: &mut World) -> bool {
    let primary_window = main_world
        .query_filtered::<&RawHandleWrapperHolder, With<PrimaryWindow>>()
        .single(main_world)

View on GitHub (pinned to 396ca72708)

Solutions

  1. Keep `TimePlugin` (part of DefaultPlugins) in the main app
  2. In a custom runner, run the main world's `time_system` every frame before rendering
  3. Never run the render app twice per main-world update

Example fix

# before
App::new()
    .add_plugins(DefaultPlugins.build().disable::<TimePlugin>())
    .add_plugins(bevy::render::RenderPlugin::default());

# after
App::new().add_plugins(DefaultPlugins); // keep TimePlugin alive
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Disabling or omitting `TimePlugin` (and its `time_system`) while still running the RenderApp; a custom runner that invokes the render app more often than it runs the main world's time system.

Common situations: Custom app runners (editor-like loops, headless render farms) that drive rendering manually; `DefaultPlugins.build().disable::<TimePlugin>()` setups that still add render plugins.

Related errors


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