bevyengine/bevy · error

PassSpanGuard::end was never called for {}

Error message

PassSpanGuard::end was never called for {}

What it means

`RecordDiagnostics::begin_pass_span` returns a `PassSpanGuard` that must be finished by calling `guard.end(&mut pass)` with the same pass; `end` records the span end and then `mem::forget`s the guard so `Drop` does not run. If the guard drops normally on any path where `end` was not called, its `Drop` impl panics with "PassSpanGuard::end was never called for {name}".

Source

Thrown at crates/bevy_render/src/diagnostic/mod.rs:241

///
/// Will panic on drop unless [`PassSpanGuard::end`] is called.
pub struct PassSpanGuard<'a, R: ?Sized, P> {
    recorder: &'a R,
    name: Cow<'static, str>,
    marker: PhantomData<P>,
}

impl<R: RecordDiagnostics + ?Sized, P: Pass> PassSpanGuard<'_, R, P> {
    /// End the span. You have to provide the same pass which was used to begin the span.
    pub fn end(self, pass: &mut P) {
        self.recorder.end_pass_span(pass);
        core::mem::forget(self);
    }
}

impl<R: ?Sized, P> Drop for PassSpanGuard<'_, R, P> {
    fn drop(&mut self) {
        panic!("PassSpanGuard::end was never called for {}", self.name)
    }
}

impl<T: RecordDiagnostics> RecordDiagnostics for Option<Arc<T>> {
    fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
    where
        N: Into<Cow<'static, str>>,
    {
        if let Some(recorder) = &self {
            recorder.record_f32(command_encoder, buffer, name);
        }
    }

    fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
    where
        N: Into<Cow<'static, str>>,
    {
        if let Some(recorder) = &self {

View on GitHub (pinned to 396ca72708)

Solutions

  1. Call `guard.end(&mut pass)` on every path before the pass ends; bind the fallible body to a variable and `?` it after ending the span
  2. Do not store `PassSpanGuard` in structs or collections; treat it as strictly scoped
  3. Keep early-exit logic in an inner function so the span always wraps it symmetrically

Example fix

// before
let span = recorder.begin_pass_span("my_pass", &mut pass);
do_work(&mut pass)?; // early return drops the span -> panic
span.end(&mut pass);

// after
let span = recorder.begin_pass_span("my_pass", &mut pass);
let result = do_work(&mut pass);
span.end(&mut pass);
result?
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Acquiring a pass span on a render/compute pass and letting it drop without `.end(pass)`: an early `?` or `return` between begin and end, storing the guard beyond its scope, or forgetting the call during refactor.

Common situations: Custom render nodes that add timestamp spans around passes and later add fallible `?` operators inside the spanned region.

Related errors


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