nautechsystems/nautilus_trader · error

Cannot deregister external order claims: {e}

Error message

Cannot deregister external order claims: {e}

What it means

This error wraps a `RefCell::try_borrow_mut` failure on the kernel cache when `deregister_external_order_claims` attempts to clear a strategy's external order claims (it calls `set_external_order_claims(strategy_id, &[])`). The cache RefCell is already borrowed elsewhere on the same thread, so the mutable borrow fails. It is an aliasing/re-entrancy error, not a claim-related domain error.

Source

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

        }

        Ok(())
    }

    /// Deregisters all external order claims owned by `strategy_id` from the shared cache.
    ///
    /// The operation is synchronous and can be called while the node is idle, after manual
    /// [`start`](Self::start) returns, or after the node stops. It cannot be called while
    /// [`run`](Self::run) or [`run_with_mode`](Self::run_with_mode) owns the node.
    ///
    /// # Errors
    ///
    /// Returns an error if the cache is already borrowed.
    pub fn deregister_external_order_claims(&self, strategy_id: StrategyId) -> anyhow::Result<()> {
        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()

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the cache borrow is released (drop guards) before deregistering.
  2. Perform deregistration outside callbacks, e.g. in the outer shutdown path.
  3. Defer the call via a queue processed where no cache borrow is held.
  4. Check the wrapped `e` text for the location of the conflicting borrow.

Example fix

// before
// inside on_stop while cache may be borrowed
node.deregister_external_order_claims(strategy_id)?;
// after
// schedule teardown outside the callback scope
telemetry_queue.push(TearDown::DeregisterClaims(strategy_id));
Defensive patterns

Strategy: try-catch

Validate before calling

if node.kernel.cache.try_borrow().is_err() {
    return Err(anyhow::anyhow!("cache already borrowed; defer claim deregistration"));
}

Try / catch

match node.deregister_external_order_claims(strategy_id) {
    Ok(()) => {},
    Err(e) if e.to_string().contains("Cannot deregister external order claims") => {
        // schedule teardown where no cache borrow is held
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `deregister_external_order_claims` while the kernel cache is borrowed — e.g. from inside an event/data callback holding a cache borrow, or while another node operation (like `add_strategy`) is mid-call on the same thread.

Common situations: Tearing down strategies from within their own `on_stop` handler while the cache borrow from dispatch is alive; interleaving strategy add/remove calls in async tasks sharing the node on one thread.

Related errors


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