nautechsystems/nautilus_trader · error

Failed to validate order client origin: {e}

Error message

Failed to validate order client origin: {e}

What it means

index_order_clients first checks whether a client_order_id is already claimed by a different execution client; any sqlx failure during that fetch_optional lookup is wrapped as 'Failed to validate order client origin'. It is a database-layer failure of the conflict-check SELECT, not the conflict itself (that is the separate bail below). Thrown so the whole transaction aborts with a clear message.

Source

Thrown at crates/infrastructure/src/sql/queries.rs:1461

        let mut transaction = pool.begin().await?;

        for (client_order_id, client_id) in claims {
            let conflicting_client_id = sqlx::query_scalar::<_, String>(
                r#"
                SELECT client_id
                FROM "order_event"
                WHERE client_order_id = $1
                  AND client_id IS NOT NULL
                  AND client_id <> $2
                LIMIT 1
            "#,
            )
            .bind(client_order_id.to_string())
            .bind(client_id.to_string())
            .fetch_optional(&mut *transaction)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to validate order client origin: {e}"))?;

            if let Some(conflicting_client_id) = conflicting_client_id {
                anyhow::bail!(
                    "Order {client_order_id} is already claimed by execution client \
                     {conflicting_client_id} and cannot be claimed by {client_id}"
                );
            }

            sqlx::query(
                r#"
                INSERT INTO "client" (id)
                VALUES ($1)
                ON CONFLICT (id) DO NOTHING
            "#,
            )
            .bind(client_id.to_string())
            .execute(&mut *transaction)
            .await

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the error's source chain for the raw sqlx/DB message.
  2. Initialize the cache schema (order_event, client tables) before calling index_order_clients.
  3. Retry on transient connection/lock errors; reduce concurrency or shorten the transaction.
  4. Verify the transaction was opened on a healthy connection.

Example fix

// before
queries::postgres::index_order_clients(&mut tx, client_order_id, client_id).await?;
// after
match queries::postgres::index_order_clients(&mut tx, client_order_id, client_id).await {
    Ok(()) => (),
    Err(e) if is_transient(&e) => retry_indexing(client_order_id, client_id).await?,
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: retry

Validate before calling

let db_ready = sqlx::query(r#"SELECT 1 FROM \"order_event\" LIMIT 1"#).fetch_optional(pool).await.is_ok();
anyhow::ensure!(db_ready, "order_event schema missing before index_order_clients");

Try / catch

for attempt in 0..3 {
    match index_clients_once(&pool, &client_order_id, &client_id).await {
        Ok(_) => break,
        Err(e) if is_transient(&e) && attempt < 2 => tokio::time::sleep(backoff(attempt)).await,
        Err(e) => return Err(e),
    }
}

Prevention

When it happens

Trigger: Calling index_order_clients when the SELECT used for the conflicting-client check fails: `order_event`/related table missing, malformed query/schema mismatch, transaction connection lost, or lock timeouts.

Common situations: Cache DB not initialized before rebuild_index/connect; concurrent writers holding locks on order_event rows; DB connection dropped mid-transaction.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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