nautechsystems/nautilus_trader · error
Invalid state trigger {self} -> {trigger}
Error message
Invalid state trigger {self} -> {trigger} What it means
Every actor, strategy, data/execution client, and engine in NautilusTrader is a Component governed by a strict lifecycle state machine (ComponentState: PreInitialized, Ready, Starting, Running, Stopping, Stopped, Degraded, Faulting, Faulted, Disposing, Disposed, and transitional states). ComponentState::transition in crates/common/src/component.rs:368 only accepts the enumerated (state, trigger) pairs and rejects every other combination. This error means a lifecycle command (initialize/start/stop/resume/reset/degrade/fault/dispose) was issued from a state where that trigger is not defined, for example Start while already Running, or Resume while Ready.
Source
Thrown at crates/common/src/component.rs:397
(Self::Running, ComponentTrigger::Degrade) => Self::Degrading,
(Self::Running, ComponentTrigger::Fault) => Self::Faulting,
(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::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::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 2114cf6f76)
Solutions
- Log or inspect component.state() before issuing the lifecycle command and only send a trigger valid for that state (start only from Ready, resume only from Stopped/Degraded, reset from Ready/Stopped).
- Guard calls with the provided predicates: is_running(), is_stopped(), is_degraded(), is_faulted(), is_disposed(), not_running().
- To reuse a stopped component, call reset() (Stopped -> Resetting -> Ready) before start(); never reuse a Disposed or Faulted component, construct a new one.
- Audit custom on_start/on_stop handlers for internal lifecycle calls that race with the trader's own stop/start sequence.
- Serialize lifecycle commands through a single task or timer so triggers cannot interleave inside transitional states.
Example fix
// before
if let Err(e) = actor.start() {
log::error!("start failed: {e}");
}
// after
use nautilus_trader::common::component::{Component, ComponentState};
if actor.state() == ComponentState::Ready {
actor.start()?;
} else {
log::debug!("skipped start, state = {:?}", actor.state());
} Defensive patterns
Strategy: validation
Validate before calling
use nautilus_trader::common::component::{Component, ComponentState};
// Only start from Ready; only resume from Stopped/Degraded
if actor.state() == ComponentState::Ready {
actor.start()?;
}
if actor.is_stopped() || actor.is_degraded() {
actor.resume()?;
} Type guard
fn trigger_is_valid(state: ComponentState, trigger: ComponentTrigger) -> bool {
use ComponentState::*;
use ComponentTrigger::*;
matches!(
(state, trigger),
(PreInitialized, Initialize)
| (Ready, Reset | Start | Dispose)
| (Resetting, ResetCompleted)
| (Starting, StartCompleted | Stop | Fault)
| (Running, Stop | Degrade | Fault)
| (Resuming, Stop | ResumeCompleted | Fault)
| (Stopping, StopCompleted | Fault)
| (Stopped, Reset | Resume | Dispose | Fault)
| (Degrading, DegradeCompleted)
| (Degraded, Resume | Stop | Fault)
| (Disposing, DisposeCompleted | Fault)
| (Faulting, FaultCompleted)
)
} Try / catch
if let Err(e) = component.start() {
if e.to_string().contains("Invalid state trigger") {
log::warn!(
"lifecycle rejected: state={:?}, retry after checking is_ready",
component.state()
);
} else {
return Err(e);
}
} Prevention
- Check state()/is_running()/is_stopped() predicates before every lifecycle call.
- Never call start()/stop() from inside on_start/on_stop handlers of the same component.
- Drive lifecycle commands from a single owner (one task/timer) to avoid interleaved triggers.
- Reset (not restart) stopped components, and construct new instances for Faulted/Disposed ones.
When it happens
Trigger: Calling component.start() when the state is anything other than Ready (e.g. a second start() on a Running component); stop() on a PreInitialized or Ready component (Stop is only valid from Starting/Running/Resuming/Degraded); resume() on Running or Ready (only valid from Stopped/Degraded); reset() while Running (only valid from Ready/Stopped); dispose() while Running (only from Ready/Stopped); or issuing any non-completion trigger while inside a transitional state such as Starting, Stopping, Resetting, Degrading, Resuming, Disposing (which accept only their Completed trigger, plus Stop/Fault where listed).
Common situations: Custom actors/strategies that call self.stop() or self.degrade() inside handlers while the trader also stops them at shutdown; tests that reuse one component instance and call start() twice; calling resume() on a component that was never stopped; trying to restart a Faulted or Disposed component instead of building a new one; lifecycle commands fired concurrently from multiple timers or tasks.
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
- Execution intent {intent_id} is not prepared for nonce {nonc
- Invalid execution transition for intent {intent_id}: {curren
- Active execution intent {intent_id} was not found
- Invalid execution transition for intent {intent_id}: {curren
- Signer not initialized; connect the client first
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/daa22f10be59c81e.
Report an issue: GitHub.