nautechsystems/nautilus_trader · error
Invalid state trigger
Error message
Invalid state trigger {self} -> {trigger} What it means
ComponentState::transition implements an explicit finite state machine for component lifecycles (PreInitialized, Ready, Starting, Running, Stopping, Degraded, Faulting, Disposing, etc.). Any (state, trigger) pair not in the table is invalid — e.g. triggering Fault on a Running component without going through Stopping first — and the call bails. This protects components from illegal lifecycle jumps.
Solutions
- Inspect the component's current state (component.state()) before issuing a transition trigger and only send triggers with a valid edge from that state.
- Use the higher-level helpers (stop_component, dispose_component, reset_component) instead of raw transition calls; they handle state checks.
- Serialize lifecycle calls — do not trigger transitions concurrently from multiple tasks/threads.
- Check prior error logs: an earlier fault may have moved the component into Faulted/Faulting, making subsequent triggers invalid; reset or dispose from there.
Example fix
// before
if let Err(e) = component.transition(ComponentTrigger::Fault) { /* Fault from Running is invalid */ }
// after
let next = component.state().transition(ComponentTrigger::Stop); // or use stop_component helper
if next.is_ok() {
component.transition(ComponentTrigger::Stop);
} else {
log::warn!("Skipping transition: {:?}", next.err());
} Defensive patterns
Strategy: try-catch
Validate before calling
fn can_transition(state: ComponentState, trigger: ComponentTrigger) -> bool {
state.transition(trigger).is_ok()
} Try / catch
match component.transition(trigger) {
Ok(new_state) => log::debug!("transitioned to {new_state}"),
Err(e) if e.to_string().starts_with("Invalid state trigger") => {
log::warn!("ignoring invalid lifecycle trigger: {e}");
}
Err(e) => return Err(e),
} Prevention
- Inspect component.state() before issuing any trigger
- Use high-level helpers (start/stop/dispose_component) instead of raw transitions
- Never interleave lifecycle triggers concurrently on one component
- Consult the state table in component.rs for valid (state, trigger) pairs before adding custom transition logic
When it happens
Trigger: Calling component transition APIs (stop/fault/dispose) in an order the state machine does not allow, e.g. sending ComponentTrigger::Fault while the component is in a state with no Fault edge, or double-triggering a transition (stop twice, dispose twice).
Common situations: Race conditions where two lifecycle calls interleave (a stop callback firing while fault handling is in progress); retrying a failed stop and hitting an unexpected state; custom actors/strategies overriding callbacks and triggering transitions manually; shutdown sequences disposing components that already faulted.
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
- Component ' ' is already mutably borrowed. This would…
- Execution intent is , not prepared for signing
- Invalid execution transition for intent
- Invalid LiveNode control state
- A transaction is being prepared; at most one transaction…
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/daa22f10be59c81e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/common/src/component.rs:402
(Self::Resuming, ComponentTrigger::Stop) => Self::Stopping,
(Self::Resuming, ComponentTrigger::ResumeCompleted) => Self::Running,
(Self::Resuming, ComponentTrigger::Fault) => Self::Faulting,
(Self::Stopping, ComponentTrigger::StopCompleted) => Self::Stopped,
(Self::Stopping, ComponentTrigger::Dispose) => Self::Disposing,
(Self::Stopping, ComponentTrigger::Fault) => Self::Faulting,
(Self::Stopped, ComponentTrigger::Reset) => Self::Resetting,
(Self::Stopped, ComponentTrigger::Resume) => Self::Resuming,
(Self::Stopped, ComponentTrigger::Dispose) => Self::Disposing,
(Self::Stopped, ComponentTrigger::Fault) => Self::Faulting,
(Self::Degrading, ComponentTrigger::DegradeCompleted) => Self::Degraded,
(Self::Degraded, ComponentTrigger::Resume) => Self::Resuming,
(Self::Degraded, ComponentTrigger::Stop) => Self::Stopping,
(Self::Degraded, ComponentTrigger::Fault) => Self::Faulting,
(Self::Disposing, ComponentTrigger::DisposeCompleted) => Self::Disposed,
(Self::Disposing, ComponentTrigger::Fault) => Self::Faulting,
(Self::Faulting, ComponentTrigger::Dispose) => Self::Disposing,
(Self::Faulting, ComponentTrigger::FaultCompleted) => Self::Faulted,
_ => anyhow::bail!("Invalid state trigger {self} -> {trigger}"),
};
Ok(new_state)
}
}
thread_local! {
static COMPONENT_REGISTRY: ComponentRegistry = ComponentRegistry::new();
}
/// Registry for storing components with runtime borrow tracking.
///
/// The registry tracks which components are currently mutably borrowed to prevent
/// multiple simultaneous mutable borrows (which would be undefined behavior).
pub struct ComponentRegistry {
components: RefCell<AHashMap<Ustr, Rc<UnsafeCell<dyn Component>>>>,
borrows: RefCell<AHashSet<Ustr>>,
}
View on GitHub (pinned to 18893faf8b)