nautechsystems/nautilus_trader · error

default client already registered

Error message

default client already registered

What it means

The engine supports at most one default execution client. set_default_client bails with this error if default_client_id is already Some, since reassigning the default client is not allowed through this API.

Source

Thrown at crates/execution/src/engine/mod.rs:396

    /// Registers a default execution client for fallback routing.
    pub fn register_default_client(&mut self, client: Box<dyn ExecutionClient>) {
        let client_id = client.client_id();
        let adapter = ExecutionClientAdapter::new(client);

        self.clients.insert(client_id, adapter);
        self.default_client_id = Some(client_id);
        log::debug!("Registered default client {client_id}");
    }

    /// Marks an already-registered client as the default for fallback routing.
    ///
    /// # Errors
    ///
    /// Returns an error if no client is registered with the given ID, or a default
    /// client has already been set.
    pub fn set_default_client(&mut self, client_id: ClientId) -> anyhow::Result<()> {
        if self.default_client_id.is_some() {
            anyhow::bail!("default client already registered");
        }

        if !self.clients.contains_key(&client_id) {
            anyhow::bail!("No client registered with ID {client_id}");
        }
        self.default_client_id = Some(client_id);
        log::debug!("Set client {client_id} as default");
        Ok(())
    }

    #[must_use]
    /// Returns a reference to the execution client registered with the given ID.
    pub fn get_client(&self, client_id: &ClientId) -> Option<&dyn ExecutionClient> {
        self.clients.get(client_id).map(|a| a.client.as_ref())
    }

    #[must_use]
    /// Returns a mutable reference to the execution client adapter registered with the given ID.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Call set_default_client only once per engine instance, before any other default assignment.
  2. If replacement is intended, call deregister_client with the current default's client ID first (tests show deregistering the default clears default_client_id), then set the new default.
  3. Guard the call: only invoke set_default_client when no default has been set yet.

Example fix

// before
engine.set_default_client(default_id)?;
engine.set_default_client(new_default_id)?; // bails
// after
engine.set_default_client(default_id)?;
engine.deregister_client(default_id)?;
engine.set_default_client(new_default_id)?;
Defensive patterns

Strategy: validation

Validate before calling

// set the default exactly once during engine construction, before any other setup runs

Try / catch

match engine.set_default_client(id) {
    Err(e) if e.to_string().contains("default client already registered") => {} // keep existing default
    other => other?,
}

Prevention

When it happens

Trigger: Calling ExecutionEngine::set_default_client after a default client has already been set in the engine's lifetime (default_client_id.is_some() == true).

Common situations: Calling set_default_client twice during node startup (e.g. config-driven setup plus programmatic setup); replacing a default client without first deregistering it; running a test fixture that calls set_default_client per test against a shared engine.

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