nautechsystems/nautilus_trader · error

Invalid NodeState value

Error message

Invalid NodeState value

What it means

`NodeState::from_u8` in crates/live/src/node/state.rs converts a raw u8 back into a `NodeState` enum and panics on any value outside 0–4. Valid values are 0=Idle, 1=Starting, 2=Running, 3=ShuttingDown, 4=Stopped. Hitting the panic means a byte that never came from `as_u8` was fed to the deserializer — data corruption, a version/schema mismatch, or hand-crafted input.

Source

Thrown at crates/live/src/node/state.rs:67

    ShuttingDown = 3,
    Stopped = 4,
}

impl NodeState {
    /// Creates a `NodeState` from its `u8` representation.
    ///
    /// # Panics
    ///
    /// Panics if the value is not a valid `NodeState` discriminant (0-4).
    #[must_use]
    pub const fn from_u8(value: u8) -> Self {
        match value {
            0 => Self::Idle,
            1 => Self::Starting,
            2 => Self::Running,
            3 => Self::ShuttingDown,
            4 => Self::Stopped,
            _ => panic!("Invalid NodeState value"),
        }
    }

    /// Returns the `u8` representation of this state.
    #[must_use]
    pub const fn as_u8(self) -> u8 {
        self as u8
    }

    /// Returns whether the state is `Running`.
    #[must_use]
    pub const fn is_running(&self) -> bool {
        matches!(self, Self::Running)
    }
}

/// Determines which lifecycle responsibilities the node owns while running.
///

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Clamp/validate the incoming u8 before conversion: only pass values produced by `NodeState::as_u8`.
  2. Delete or regenerate the corrupted/legacy persisted state file so the node starts from a valid state.
  3. Wrap the conversion in a checked helper returning Option/Result instead of panicking on unknown bytes.

Example fix

// before
let state = NodeState::from_u8(raw_byte); // panics if raw_byte > 4

// after
let state = NodeState::try_from_u8(raw_byte) // or:
let state = match raw_byte { 0..=4 => NodeState::from_u8(raw_byte), _ => NodeState::Idle };
// ideally add a checked variant in the crate:
// pub const fn try_from_u8(v: u8) -> Option<NodeState>
Defensive patterns

Strategy: validation

Validate before calling

// before deserializing
if !(0..=4).contains(&raw) { return Err(format!("invalid NodeState byte: {raw}")); }

Type guard

fn is_valid_node_state(v: u8) -> bool { v <= 4 }

Try / catch

// panics are not catchable in Rust; use a checked wrapper:
fn parse_state(v: u8) -> Option<NodeState> { (v <= 4).then(|| NodeState::from_u8(v)) }

Prevention

When it happens

Trigger: Deserializing a persisted/configured node state byte that is >= 5 (or from a different enum version) into NodeState via from_u8; loading a state file written by a newer/older NautilusTrader version with shifted enum ordinals; manually constructing the u8 in scripts or tests.

Common situations: Upgrade/downgrade across versions where the NodeState representation changed; corrupted state files or storage; FFI/binding layers (Python/other) passing wrong integers; hand-edited config values.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/e4a6c50d2f535641. Report an issue: GitHub.