bevyengine/bevy · error

Component already has an on_discard hook

Error message

Component already has an on_discard hook

What it means

Panic in ComponentLifecycleHook registration (on_discard): try_on_discard returned None, meaning an on_discard hook is already stored for this component. The expect enforces one hook per lifecycle event since the storage holds an Option, not a list.

Source

Thrown at crates/bevy_ecs/src/lifecycle.rs:229

    /// such as being replaced (with `.insert`) or removed.
    ///
    /// If this component is inserted onto an entity that already has it, this hook will run before the value is replaced,
    /// allowing access to the previous data just before it is dropped.
    /// This hook does *not* run if the entity did not already have this component.
    ///
    /// An `on_discard` hook always runs before any `on_remove` hooks (if the component is being removed from the entity).
    ///
    /// # Warning
    ///
    /// The hook won't run if the component is already present and is only mutated, such as in a system via a query.
    /// As a result, this needs to be combined with immutable components to serve as a mechanism for reliably updating indexes and other caches.
    ///
    /// # Panics
    ///
    /// Will panic if the component already has an `on_discard` hook
    pub fn on_discard(&mut self, hook: ComponentHook) -> &mut Self {
        self.try_on_discard(hook)
            .expect("Component already has an on_discard hook")
    }

    /// Register a [`ComponentHook`] that will be run when this component is removed from an entity.
    /// Despawning an entity counts as removing all of its components.
    ///
    /// # Panics
    ///
    /// Will panic if the component already has an `on_remove` hook
    pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self {
        self.try_on_remove(hook)
            .expect("Component already has an on_remove hook")
    }

    /// Register a [`ComponentHook`] that will be run for each component on an entity when it is despawned.
    ///
    /// # Panics
    ///
    /// Will panic if the component already has an `on_despawn` hook

View on GitHub (pinned to 396ca72708)

Solutions

  1. Fold additional logic into the existing on_discard hook
  2. Locate and remove the earlier .on_discard(...) registration (often from another plugin)
  3. Prefer try_on_discard with explicit conflict handling in library code
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at crates/bevy_ecs/src/lifecycle.rs:229 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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