nautechsystems/nautilus_trader · error
Derive session task admission is closed: {e}
Error message
Derive session task admission is closed: {e} What it means
start_ws_dispatch obtains a spawner from the session_tasks TaskGroup to spawn the WS message dispatch loop. If the TaskGroup's admission is closed (the group is shutting down or not open), spawner() fails and the error is wrapped with this message. It means the client tried to start dispatch while its session task group could no longer accept new tasks.
Source
Thrown at crates/adapters/derive/src/execution.rs:454
}
Ok(())
}
fn start_ws_dispatch(
&self,
rx: tokio::sync::mpsc::UnboundedReceiver<DeriveWsMessage>,
) -> anyhow::Result<()> {
let emitter = self.emitter.clone();
let account_id = self.core.account_id;
let clock = self.clock;
let cancellation = self.cancellation_token.clone();
let dispatch_state = self.dispatch_state.clone();
let reconciliation = self.reconciliation_context();
let is_connected = Arc::clone(&self.is_connected);
let session_spawner = self
.session_tasks
.spawner()
.map_err(|e| anyhow::anyhow!("Derive session task admission is closed: {e}"))?;
self.session_tasks.spawn(async move {
let mut rx = rx;
loop {
tokio::select! {
biased;
() = cancellation.cancelled() => break,
maybe = rx.recv() => {
match maybe {
Some(DeriveWsMessage::Reconnected) => {
let context = reconciliation.clone();
let task_cancellation = cancellation.clone();
if let Err(e) = session_spawner.spawn(async move {
tokio::select! {
() = task_cancellation.cancelled() => {}
result = context.recover_after_reconnect() => {View on GitHub (pinned to 18893faf8b)
Solutions
- Only call connect while the client is stopped-but-not-shut-down; ensure disconnect/stop has fully reset the task groups before reconnecting, or recreate the client.
- Guard against concurrent connect/stop calls with proper lifecycle management.
- Check the inner TaskGroup error to confirm the group's state (closed vs already started).
- If reconnect support is needed, use the client's supported reconnect path rather than calling connect after teardown.
Defensive patterns
Strategy: try-catch
Validate before calling
// Do not call connect if the group is closing:
if client.is_shutting_down() { return Err("cannot connect during shutdown".into()); } Try / catch
match exec_client.connect().await {
Ok(()) => (),
Err(e) if e.to_string().contains("admission is closed") => {
// recreate the client; its task groups are closed
}
Err(e) => return Err(e),
} Prevention
- Call connect exactly once per client lifecycle; recreate the client for reconnects
- Serialize connect/stop calls under a single owner or mutex
- Never invoke connect concurrently with disconnect/stop
When it happens
Trigger: Calling connect (which invokes start_ws_dispatch) on a Derive execution client whose session_tasks group is already closed/shutting down — e.g. connect after disconnect, or racing connect against shutdown.
Common situations: Calling connect concurrently with stop/disconnect; reconnect logic firing after the client was halted; lifecycle misuse where connect is invoked on a disposed client instance.
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
- Failed to start Derive session generation: {e}
- Failed to start Derive task generation: {e}
- Failed to start Betfair data session tasks: {e}
- Failed to start Betfair data command tasks: {e}
- Failed to start Betfair session generation: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/6f91af9043986a81.
Report an issue: GitHub.