nautechsystems/nautilus_trader · error

Cannot roll back external order claims: {e}

Error message

Cannot roll back external order claims: {e}

What it means

This error wraps a `RefCell::try_borrow_mut` failure on the kernel cache during `rollback_external_order_claims`, which reads the strategy's current claims, filters out the given instrument IDs, and rewrites the retained set. If the cache RefCell is already borrowed on the same thread, the borrow fails and this message is raised. It is called by `add_strategy` during rollback of a failed registration.

Source

Thrown at crates/live/src/node/mod.rs:2819

        self.kernel
            .cache
            .try_borrow_mut()
            .map_err(|e| anyhow::anyhow!("Cannot deregister external order claims: {e}"))?
            .set_external_order_claims(strategy_id, &[])?;

        Ok(())
    }

    pub(crate) fn rollback_external_order_claims(
        &self,
        strategy_id: StrategyId,
        instrument_ids: &[InstrumentId],
    ) -> anyhow::Result<()> {
        let mut cache = self
            .kernel
            .cache
            .try_borrow_mut()
            .map_err(|e| anyhow::anyhow!("Cannot roll back external order claims: {e}"))?;
        let retained: Vec<_> = cache
            .external_order_claim_instrument_ids(Some(strategy_id))
            .into_iter()
            .filter(|instrument_id| !instrument_ids.contains(instrument_id))
            .collect();
        cache.set_external_order_claims(strategy_id, &retained)
    }

    /// Adds an execution algorithm to the trader.
    ///
    /// Execution algorithms are registered in both the component registry (for lifecycle
    /// management) and the actor registry (for data callbacks via msgbus).
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - The node is currently running.
    /// - An execution algorithm with the same ID is already registered.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure no outstanding cache borrow exists when `add_strategy` (and thus its rollback) runs.
  2. Register strategies at setup time, before the node's event loop holds cache borrows.
  3. If this appears during rollback, look one level up: the original failure plus a live cache borrow; fix the outer borrow scope.
  4. Inspect the wrapped `e` for the conflicting borrow site.

Example fix

// before
let cache = node.kernel.cache.borrow();
node.add_strategy(strategy, ...)?; // rollback path needs cache mutably
// after
drop(node.kernel.cache.borrow());
node.add_strategy(strategy, ...)?;
Defensive patterns

Strategy: try-catch

Validate before calling

if node.kernel.cache.try_borrow_mut().is_err() {
    return Err(anyhow::anyhow!("cache already mutably borrowed; rollback will fail"));
}

Try / catch

match node.add_strategy(strategy, ids) {
    Err(e) if e.to_string().contains("Cannot roll back external order claims") => {
        // fix outer cache borrow scope, then verify claims state via external_order_claim_instrument_ids
    }
    other => other?,
}

Prevention

When it happens

Trigger: A failed `add_strategy` triggers rollback while the cache RefCell is still held by another borrow on the same thread — typically because the failure path runs inside code that itself borrowed the cache, or user code holds a cache borrow while adding strategies.

Common situations: Nested strategy registration failures during node startup; rolling back claims from a callback where the cache borrow is alive; concurrent (same-thread) node operations interleaved with strategy registration.

Related errors


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