linebender/druid · error

commands should be dispatched via dispatch_cmd

Error message

commands should be dispatched via dispatch_cmd

What it means

druid's AppHandler::do_window_event is the path for ordinary window events only. Command and targeted-command events must travel through `dispatch_cmd`, which routes them to the correct target window/delegate; receiving one here means a command was injected into the wrong dispatch path, so the handler panics to surface the invariant violation.

Solutions

  1. Route commands through `AppHandler::dispatch_cmd` (or higher-level `WidgetExt::submit_command` / `EventCtx::submit_command`) instead of feeding them into `do_window_event`
  2. In custom dispatch code, match on the event kind and forward Command variants to dispatch_cmd, passing only other events to do_window_event
  3. If you only hold a generic Event, inspect it: commands should never be re-dispatched manually; use the command's WindowId target with dispatch_cmd

Example fix

// before
handler.do_window_event(window_id, Event::Command(cmd));
// after
handler.dispatch_cmd(window_id, cmd); // or ctx.submit_command(cmd)
Defensive patterns

Strategy: validation

Validate before calling

// Route by event kind before touching do_window_event
match &event {
    Event::Command(_) | Event::Internal(InternalEvent::TargetedCommand(_)) => handler.dispatch_cmd(window_id, event.unwrap_cmd()),
    _ => handler.do_window_event(window_id, event),
}

Prevention

When it happens

Trigger: Delivering `Event::Command` or `Event::Internal(InternalEvent::TargetedCommand(..))` directly into `do_window_event` — typically by calling the window-handler plumbing yourself, custom event loops, or code that bypasses `AppHandler::dispatch_cmd`.

Common situations: Custom main-loop or test harnesses pushing events straight to the handler; monkey-patched or forked druid code that forwards all window events uniformly; re-injecting a captured Event object back into the handler.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of linebender/druid@0f8b1195e4 (2026-09-10). Data as JSON: /api/errors/186f2ef2a16c8d08. Report an issue: GitHub.

Appendix: source

Thrown at druid/src/win_handler.rs:419

                    let event = Event::Command(cmd.clone());
                    if w.event(&mut self.command_queue, event, &mut self.data, &self.env)
                        .is_handled()
                    {
                        return Handled::Yes;
                    }
                }
            }
            Target::Auto => {
                tracing::error!("{:?} reached window handler with `Target::Auto`", cmd);
            }
        }
        Handled::No
    }

    fn do_window_event(&mut self, source_id: WindowId, event: Event) -> Handled {
        match event {
            Event::Command(..) | Event::Internal(InternalEvent::TargetedCommand(..)) => {
                panic!("commands should be dispatched via dispatch_cmd");
            }
            _ => (),
        }

        // if the event was swallowed by the delegate we consider it handled?
        let event = match self.delegate_event(source_id, event) {
            Some(event) => event,
            None => return Handled::Yes,
        };

        if let Some(win) = self.windows.get_mut(source_id) {
            win.event(&mut self.command_queue, event, &mut self.data, &self.env)
        } else {
            Handled::No
        }
    }

    fn show_context_menu(&mut self, window_id: WindowId, cmd: &Command) {

View on GitHub (pinned to 0f8b1195e4)