nautechsystems/nautilus_trader · error · anyhow::Error

Cannot set external order claims: {e}

Error message

Cannot set external order claims: {e}

What it means

While setting external order instrument IDs, the strategy borrows the shared Cache mutably via `cache.try_borrow_mut()`. If the Cache RefCell is already borrowed (by this or another actor on the same thread) the borrow fails and the error is re-wrapped as `Cannot set external order claims: {e}`.

Source

Thrown at crates/trading/src/strategy/mod.rs:140

    ///
    /// Returns an error if the strategy is not registered, the cache is already borrowed, an
    /// instrument is repeated, or an instrument is claimed by another strategy.
    fn set_external_order_instrument_ids(
        &mut self,
        instrument_ids: Vec<InstrumentId>,
    ) -> anyhow::Result<()>
    where
        Self: StrategyNative,
    {
        let core = StrategyNative::strategy_core_mut(self);
        let strategy_id = registered_strategy_id(core)?;
        if !core.actor.is_registered() {
            anyhow::bail!("Strategy {strategy_id} is not registered with a trader");
        }
        let cache = core.cache_rc();
        cache
            .try_borrow_mut()
            .map_err(|e| anyhow::anyhow!("Cannot set external order claims: {e}"))?
            .set_external_order_claims(strategy_id, &instrument_ids)?;
        core.config.external_order_instrument_ids = Some(instrument_ids);
        Ok(())
    }

    /// Returns the runtime strategy ID, when configured or registered.
    fn strategy_id(&self) -> Option<StrategyId>
    where
        Self: StrategyNative,
    {
        StrategyNative::strategy_core(self).strategy_id()
    }

    /// Returns the user-facing order creation API.
    fn order(&self) -> OrderApi<'_>
    where
        Self: StrategyNative,
    {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Call `set_external_order_instrument_ids` from `on_start` or another point where no cache borrow is held.
  2. Do not retain cache borrow guards across calls; drop any `cache` handle obtained earlier before invoking.
  3. Restructure so the claims are set outside any cache iteration/dispatch context.
  4. If `{e}` indicates a persistent borrow, look for leaked borrows in custom actor code on the same thread.

Example fix

// before
def on_start(self):
    orders = self.cache.orders_open()  # holds an internal borrow
    self.set_external_order_instrument_ids([...])  # fails: cache already borrowed
// after
def on_start(self):
    self.set_external_order_instrument_ids([...])
    orders = self.cache.orders_open()
Defensive patterns

Strategy: validation

Try / catch

try:
    self.set_external_order_instrument_ids(ids)
except Exception as e:
    if 'currently borrowed' in str(e):
        self.clock.set_timer('retry-claims', 0.001, callback=self._retry_claims)
    else:
        raise

Prevention

When it happens

Trigger: Calling `set_external_order_instrument_ids` while the Cache is mutably borrowed — e.g. from within a callback that itself holds a cache borrow, or nested cache access during dispatch on the same thread.

Common situations: Holding a cache reference (e.g. from `cache.orders()` iteration or a previous try_borrow_mut result) across the call; calling claims setup from inside `on_event`/handler code while the engine is mid-dispatch; deep call chains where an outer function still holds the borrow.

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/441646b17870c2b1. Report an issue: GitHub.