nautechsystems/nautilus_trader · error · anyhow::Error

Failed to update execution intent {intent_id}: {e}

Error message

Failed to update execution intent {intent_id}: {e}

What it means

Wrapped sqlx error from the UPDATE execution_intent SET status = $2, active = $3 statement in record_execution_status (database.rs:3667-3679), which advances the intent's status and computes the active flag after a receipt observation. The row was already locked FOR UPDATE earlier in the same transaction, so lock contention on this specific statement is unlikely; failures are typically connection loss, statement timeout, or a constraint violation on the status/active columns.

Source

Thrown at crates/adapters/blockchain/src/cache/database.rs:3679

        .map_err(|e| anyhow::anyhow!("Failed to update execution hash {transaction_hash}: {e}"))?;
        anyhow::ensure!(
            hash_result.rows_affected() == 1,
            "Execution transaction hash {transaction_hash} was not found for intent {intent_id}"
        );

        sqlx::query(
            "
            UPDATE execution_intent
            SET status = $2, active = $3, updated_at = NOW()
            WHERE id = $1
            ",
        )
        .bind(intent_id)
        .bind(status.as_str())
        .bind(active)
        .execute(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to update execution intent {intent_id}: {e}"))?;

        let transition_key = format!(
            "{}:{transaction_hash}:{}:{}",
            status.as_str(),
            block_number.map_or_else(|| "none".to_string(), |value| value.to_string()),
            block_hash.unwrap_or("none")
        );
        sqlx::query(
            "
            INSERT INTO execution_transaction_transition (
                intent_id, transaction_hash_id, transition_key, from_status, to_status,
                block_number, block_hash
            )
            SELECT $1, id, $3, $4, $5, $6, $7
            FROM execution_transaction_hash
            WHERE intent_id = $1 AND transaction_hash = $2
            ON CONFLICT (intent_id, transition_key) DO NOTHING
            ",

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Read the {e} cause: downcast to sqlx::Error and inspect as_database_error() for the failing constraint or SQLSTATE
  2. Apply pending migrations so execution_intent matches the status/active/updated_at columns
  3. Retry the whole call on transient classes (connection, 40001/40P01/57014); the enclosing transaction rolled back so the retry is idempotent
  4. If a CHECK/domain constraint rejected the status string, fix the TransactionStatus value being passed at the call site
Defensive patterns

Strategy: retry

Type guard

fn is_transient_db_error(err: &anyhow::Error) -> bool {
    err.downcast_ref::<sqlx::Error>().map_or(false, |e| {
        matches!(e, sqlx::Error::PoolTimedOut | sqlx::Error::PoolClosed | sqlx::Error::Io(_))
            || e.as_database_error().and_then(|d| d.code()).map_or(false, |c| {
                matches!(c.as_ref(), "40001" | "40P01" | "55P03" | "57014" | "08000" | "08003" | "08006")
            })
    })
}

Try / catch

match db.record_execution_status(...).await {
    Ok(()) => {}
    Err(e) if is_transient_db_error(&e) => retry_with_backoff(e), // rollback makes replay safe
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: The database connection dying between the hash UPDATE and the intent UPDATE; a CHECK constraint on execution_intent.status rejecting the bound status string; schema drift where the active or updated_at column is missing; a serialization failure aborting the transaction at this point.

Common situations: Postgres restart or failover mid-transaction; running against a schema from an older migration set that lacks the active column; a status enum/domain on the column that does not include the value being written.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/0b078a5c16d42d7a. Report an issue: GitHub.