nautechsystems/nautilus_trader · error

Failed to commit recoverable transition: {e}

Error message

Failed to commit recoverable transition: {e}

What it means

`mark_execution_intent_recoverable` opens a PostgreSQL transaction, locks the execution intent, marks it 'recoverable', records a transition row, then commits. This error wraps a failure of the final `transaction.commit().await` call, meaning all in-transaction work (intent status change and transition insert) was rolled back by the database.

Source

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

            "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,
        raw_transaction: &[u8],
    ) -> anyhow::Result<ExecutionTransactionHashRow> {
        self.add_execution_transaction_payload(
            intent_id,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check database connectivity and pool health (sqlx pool options: acquire_timeout, max_lifetime, idle_timeout) and retry `mark_execution_intent_recoverable`; the operation is idempotent-safe to retry since the transaction rolled back
  2. Inspect Postgres logs for the matching commit failure (deadlock, serialization failure 40001, connection reset) and address the root cause
  3. Shorten transaction scope / ensure no long-held locks on the `execution_intent` row (e.g. concurrent migrations or other FOR UPDATE holders)
  4. Add retry-with-backoff around transient SQLSTATE classes (08000 connection exceptions, 40001 serialization failures)

Example fix

// before
transaction.commit().await
    .map_err(|e| anyhow::anyhow!("Failed to commit recoverable transition: {e}"))?;
// after
transaction.commit().await
    .map_err(|e| anyhow::anyhow!("Failed to commit recoverable transition: {e}"))
    .inspect_err(|e| tracing::error!(intent_id, "commit failed, intent remains 'prepared': {e:#}"))?;
Defensive patterns

Strategy: retry

Validate before calling

// Check DB reachability before the call
let healthy = sqlx::query("SELECT 1").execute(&db.pool).await.is_ok();
anyhow::ensure!(healthy, "database unreachable, defer recoverable transition");

Try / catch

match db.mark_execution_intent_recoverable(intent_id).await {
    Ok(()) => {}
    Err(e) if is_transient_sqlstate(&e) => schedule_retry(intent_id, e),
    Err(e) => return Err(e),
}

fn is_transient_sqlstate(e: &anyhow::Error) -> bool {
    let msg = format!("{e:#}");
    ["08000", "40001", "connection", "closed"].iter().any(|s| msg.contains(s))
}

Prevention

When it happens

Trigger: The COMMIT statement itself fails after the UPDATE/INSERT succeeded inside the transaction — typically a lost or dropped database connection, connection pool timeout, deadlock detection aborting the transaction, serialization failure under repeatable-read, or the database shutting down mid-commit.

Common situations: Database restarted or failed over during the operation; idle connection in the pool was terminated by a firewall/load balancer before commit; statement_timeout or lock timeout hit on the FOR UPDATE row lock; running a migration that locks `execution_intent` concurrently, causing the commit/lock wait to abort.

Related errors


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