nautechsystems/nautilus_trader · error

Cannot clear external order claims: {e}

Error message

Cannot clear external order claims: {e}

What it means

Raised in `retire_strategy` when the cache `Rc<RefCell<Cache>>` cannot be mutably borrowed while attempting to clear external order claims for the retiring strategy. A live mutable borrow elsewhere (RefCell already borrowed) prevents the check, and the strategy would risk being disposed while still tracked.

Source

Thrown at crates/system/src/trader.rs:1459

    /// through a bulk operation leaves the trader's bookkeeping consistent with the registries.
    fn retire_actor(&mut self, actor_id: ActorId) -> anyhow::Result<()> {
        Self::dispose_registered_component(actor_id.inner())?;

        self.release_component(ComponentId::from(actor_id));
        self.actor_ids.retain(|id| id != &actor_id);
        self.actor_state_callbacks.remove(&actor_id);

        Ok(())
    }

    /// Disposes a strategy, then releases everything its registration created.
    fn retire_strategy(&mut self, strategy_id: StrategyId) -> anyhow::Result<()> {
        // Check before disposal so a borrow failure cannot leave a disposed strategy registered.
        {
            let _cache = self
                .cache
                .try_borrow_mut()
                .map_err(|e| anyhow::anyhow!("Cannot clear external order claims: {e}"))?;
        }

        Self::dispose_registered_component(strategy_id.inner())?;

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

        self.remove_strategy_subscriptions(strategy_id);
        self.release_component(ComponentId::from(strategy_id));
        self.strategy_ids.retain(|id| id != &strategy_id);
        self.strategy_state_callbacks.remove(&strategy_id);
        self.strategy_stop_fns.remove(&strategy_id);

        Ok(())
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Avoid calling trader retirement/removal APIs from inside code that holds a cache borrow (e.g. inside strategy handlers)
  2. Defer retirement to outside the current callback (queue it and execute after the handler returns)
  3. Inspect the chained BorrowError context to find who holds the borrow
  4. Refactor to use the trader's message bus/command pattern instead of direct nested calls

Example fix

// before: retiring from inside an on_event handler that borrows cache
def on_event(self, event):
    self.trader.remove_strategy(self.id)
// after: defer via message/command
def on_event(self, event):
    self.msgbus.send("trader.remove_strategy", self.id)  # executed outside the borrow
Defensive patterns

Strategy: try-catch

Try / catch

try:
    trader.remove_strategy(strategy_id)
except Exception as e:
    if "Cannot clear external order claims" in str(e):
        logging.error("cache is borrowed elsewhere; defer removal: %s", e)
    raise

Prevention

When it happens

Trigger: Calling `remove_strategy`, `clear_strategies`, or `dispose_components` while another component (actor, strategy, or callback on the same thread) holds a mutable borrow of the cache, causing `try_borrow_mut` to fail with a BorrowError.

Common situations: Reentrant calls into the trader from within a strategy/actor callback that itself holds the cache borrow; nested strategy disposal while iterating cache state; calling trader lifecycle methods from inside event handlers.

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


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