nautechsystems/nautilus_trader · error

book channel present

Error message

book channel present

What it means

`SubscriptionDispatchState::activate` records which websocket channel (e.g. `summary`, `trades`) owns a subscription. For book-delta and book-depth10 owners it expects the channel string to be present and panics otherwise. The invariant is that book-channel subscriptions are only activated after the channel assignment is known.

Source

Thrown at crates/adapters/derive/src/data.rs:1905

        let mut state = self.registry.state.lock();
        let removed = state.remove(owner)?;
        self.dispatch.deactivate(owner, removed.channel_empty);
        Some(removed)
    }

    fn has_owners(&self, channel: &str) -> bool {
        let _guard = self.lock.lock();
        self.registry.state.lock().has_owners(channel)
    }
}

impl SubscriptionDispatchState {
    fn activate(&self, owner: ChannelOwner, channel: Option<&str>) {
        match owner {
            ChannelOwner::BookDeltas(instrument_id) => {
                self.active_book_delta_channels.insert(
                    instrument_id,
                    channel.expect("book channel present").to_string(),
                );
            }
            ChannelOwner::BookDepth10(instrument_id) => {
                self.active_book_depth10_channels.insert(
                    instrument_id,
                    channel.expect("book channel present").to_string(),
                );
            }
            ChannelOwner::Ticker {
                instrument_id,
                feed,
            } => {
                self.active_ticker_channels.insert(
                    instrument_id,
                    channel.expect("ticker channel present").to_string(),
                );
                self.ticker_subscriptions(feed).insert(instrument_id);
            }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the subscribe-confirmation handler stores the channel name before calling `activate` for book owners.
  2. On reconnect, re-derive the channel ("book" / "depth10") for book subscriptions rather than passing None.
  3. Change `activate` to accept `&str` for book owners (make the requirement explicit) or fall back to the default channel name instead of panicking.

Example fix

// before
self.activate(ChannelOwner::BookDeltas(instrument_id), None);
// after
self.activate(ChannelOwner::BookDeltas(instrument_id), Some("book"));
Defensive patterns

Strategy: validation

Validate before calling

// before activate
if channel.is_none() && matches!(owner, ChannelOwner::BookDeltas(_) | ChannelOwner::BookDepth10(_)) {
    // defer activation until subscribe confirmation arrives
    return;
}

Try / catch

let result = std::panic::catch_unwind(|| state.activate(owner, channel));
if result.is_err() { log_and_resubscribe(); }

Prevention

When it happens

Trigger: Activating a `ChannelOwner::BookDeltas` or `BookDepth10` owner while the `channel` parameter is `None` — i.e. the subscription activation path is invoked before the websocket subscribe confirmation carrying the channel name arrives.

Common situations: A websocket reconnect/replay path that re-activates cached book subscriptions without re-deriving their channel names, an out-of-order message where activation precedes the subscribe ack, or a code change adding a new book owner that forgets to pass its channel.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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