nautechsystems/nautilus_trader · error

Failed to record recoverable transition: {e}

Error message

Failed to record recoverable transition: {e}

What it means

After successfully flipping the intent to 'recoverable', the code inserts an audit row into execution_transaction_transition (transition_key 'recoverable'); if that INSERT fails, the error "Failed to record recoverable transition: {e}" is raised. The whole transaction then rolls back, so the intent remains 'prepared' — the failure is atomic.

Source

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

        .await
        .map_err(|e| anyhow::anyhow!("Failed to mark execution intent recoverable: {e}"))?;
        anyhow::ensure!(
            result.rows_affected() == 1,
            "Execution intent {intent_id} is not recoverable from preparation"
        );
        sqlx::query(
            "
            INSERT INTO execution_transaction_transition (
                intent_id, transition_key, from_status, to_status
            ) VALUES ($1, 'recoverable', $2, 'recoverable')
            ON CONFLICT (intent_id, transition_key) DO NOTHING
            ",
        )
        .bind(intent_id)
        .bind(current_status)
        .execute(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to record recoverable transition: {e}"))?;
        transaction
            .commit()
            .await
            .map_err(|e| anyhow::anyhow!("Failed to commit recoverable transition: {e}"))?;
        Ok(())
    }

    /// Persists a signed transaction and advances its intent before broadcast.
    ///
    /// # Errors
    ///
    /// Returns an error if the intent is not prepared, lacks a nonce, conflicts with a stored
    /// hash, or persistence fails.
    pub async fn add_execution_transaction_hash(
        &self,
        intent_id: i64,
        chain_id: u32,
        transaction_hash: &str,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the wrapped sqlx error chain for the concrete constraint/message (e.g. column does not exist, foreign key violation).
  2. Verify the execution_transaction_transition schema matches the adapter's expected columns (intent_id, transition_key, from_status, to_status).
  3. Retry the operation after transient errors — the transaction rolled back atomically, so the intent stays 'prepared' and the transition is safe to redo.
  4. Check grants on the transition table and database health (disk, connections) if the failure repeats.
Defensive patterns

Strategy: retry

Try / catch

// Atomic rollback means safe retry after transient insert failures
match db.mark_execution_intent_recoverable(id).await {
    Err(e) if is_transient_db_error(&e) => retry_with_backoff(id),
    Err(e) => return Err(e),
    Ok(()) => {},
}

Prevention

When it happens

Trigger: The INSERT INTO execution_transaction_transition fails: constraint violation (e.g. a foreign key or NOT NULL on from_status), schema mismatch after a migration, connection loss, or deadlock — anything that makes sqlx's execute return Err.

Common situations: A migration changed execution_transaction_transition (renamed columns, added a required column) while the adapter runs older expectations; database connection dropped after long transaction hold; permissions revoked on the transition table; disk-full or tablespace errors on the audit table.

Related errors


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