nautechsystems/nautilus_trader · error · anyhow::Error

Failed to record execution transition: {e}

Error message

Failed to record execution transition: {e}

What it means

Wrapped sqlx error from the INSERT INTO execution_transaction_transition ... SELECT ... FROM execution_transaction_hash ... ON CONFLICT (intent_id, transition_key) DO NOTHING statement in record_execution_status (database.rs:3687-3708). This appends the audit row for the observed transition; the ON CONFLICT clause makes repeated observations of the same (status, hash, block) idempotent, so a unique violation on that key is impossible - the error is another database-level fault.

Source

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

                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
            ",
        )
        .bind(intent_id)
        .bind(transaction_hash)
        .bind(transition_key)
        .bind(current_status)
        .bind(status.as_str())
        .bind(block_number_db)
        .bind(block_hash)
        .execute(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to record execution transition: {e}"))?;

        transaction
            .commit()
            .await
            .map_err(|e| anyhow::anyhow!("Failed to commit execution transition: {e}"))?;
        Ok(())
    }

    /// Loads the active intent owned by a signer, if one exists.
    ///
    /// # Errors
    ///
    /// Returns an error if the query fails.
    pub async fn get_active_execution_intent(
        &self,
        chain_id: u32,
        wallet_address: &str,
    ) -> anyhow::Result<Option<ExecutionIntentRow>> {

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Inspect the {e} root cause via downcast to sqlx::Error to see whether it is a connection, FK, or constraint failure
  2. Verify the execution_transaction_transition table exists with the expected columns by running the project migrations
  3. Retry the entire record_execution_status call for transient causes; the transaction rolled back and the transition_key deduplication keeps the retry safe
  4. If a length constraint is reported for transition_key, widen the column in a migration rather than truncating keys (they encode reorg identity)
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),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Connection loss or timeout while inserting the transition row; a foreign-key violation if transaction_hash_id cannot be resolved (the inner SELECT reads the hash row in the same transaction, so this implies the hash UPDATE in error 81 did not commit); a column-length or CHECK violation if transition_key or from_status/to_status exceed their column definitions after schema drift.

Common situations: Long transition_key strings (status:hash:block_number:block_hash) overflowing a VARCHAR-limited transition_key column on older schemas; Postgres failover between the hash UPDATE and this insert; migrations adding the transition table never applied.

Related errors


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