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
- 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
- Inspect Postgres logs for the matching commit failure (deadlock, serialization failure 40001, connection reset) and address the root cause
- Shorten transaction scope / ensure no long-held locks on the `execution_intent` row (e.g. concurrent migrations or other FOR UPDATE holders)
- 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
- Tune sqlx pool settings (acquire_timeout, max_lifetime, idle_timeout) to survive firewalls/load balancers that kill idle connections
- Retry commit failures with backoff — the transaction rolls back atomically, so retrying is safe
- Avoid running schema migrations concurrently with intent state transitions
- Monitor Postgres logs for deadlocks and lock timeouts on execution_intent
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
- Failed to assign nonce {nonce} to execution intent: {e}
- Failed to start signed transaction persistence: {e}
- Failed to persist finality verification: {e}
- Failed to record verified finality receipt: {e}
- Failed to insert into position_event table: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/1ce9e5b50ec43964.
Report an issue: GitHub.