nautechsystems/nautilus_trader · error

Cannot register external order claims: {e}

Error message

Cannot register external order claims: {e}

What it means

This error wraps a `RefCell::try_borrow_mut` failure when `register_external_order_claims` tries to mutably borrow the kernel's cache to register instrument-ID claims for a strategy. The cache is a shared `RefCell`; if it is already borrowed (mutably or immutably) on the same thread, the borrow fails and this message is raised. The inner `register_external_order_claims` call errors are propagated separately with their own messages.

Source

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

    /// Registers external order claims in the shared cache.
    ///
    /// It can be called while the node is idle, after manual [`start`](Self::start) returns, or
    /// after the node stops. A running strategy can update its own claims through
    /// `Strategy::set_external_order_instrument_ids`.
    ///
    /// # Errors
    ///
    /// Returns an error without changing the cache if the cache is already borrowed, the request
    /// repeats an instrument, or any requested instrument already has a claim.
    pub fn register_external_order_claims(
        &self,
        strategy_id: StrategyId,
        instrument_ids: &[InstrumentId],
    ) -> anyhow::Result<()> {
        self.kernel
            .cache
            .try_borrow_mut()
            .map_err(|e| anyhow::anyhow!("Cannot register external order claims: {e}"))?
            .register_external_order_claims(strategy_id, instrument_ids)?;

        if !instrument_ids.is_empty() {
            log::info!("Registered external order claims for {strategy_id}: {instrument_ids:?}");
        }

        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.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Drop any live borrow of the kernel cache before calling this method.
  2. Register external order claims during node setup, before `run()` starts the event loop.
  3. Defer registration out of callbacks into the caller that owns the node.
  4. Read the wrapped `e` message: Pyo3 reports where the conflicting borrow originates.

Example fix

// before
let cache = node.kernel.cache.borrow();
node.register_external_order_claims(strategy_id, &ids)?;
// after
drop(node.kernel.cache.borrow());
node.register_external_order_claims(strategy_id, &ids)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the kernel cache is not currently borrowed
if node.kernel.cache.try_borrow().is_err() {
    return Err(anyhow::anyhow!("cache already borrowed; defer claim registration"));
}

Try / catch

match node.register_external_order_claims(strategy_id, &ids) {
    Ok(()) => {},
    Err(e) if e.to_string().contains("Cannot register external order claims") => {
        // release cache borrows and retry, or defer to setup phase
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `register_external_order_claims` (directly or via `add_strategy`) while the kernel cache RefCell is held — e.g. inside a data/event callback that has borrowed the cache, or nested registration calls on one thread.

Common situations: Adding strategies from within a running node's callbacks; user code holding `node.kernel.cache.borrow()` while adding strategies; re-entrant `add_strategy` from `on_start` of a previously registered strategy.

Related errors


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