nautechsystems/nautilus_trader · error
Failed to mark execution intent recoverable: {e}
Error message
Failed to mark execution intent recoverable: {e} What it means
The UPDATE that flips execution_intent from 'prepared' to 'recoverable' failed at the database level; the underlying sqlx error is wrapped as "Failed to mark execution intent recoverable: {e}". This is a persistence failure, not a business-rule rejection (rows_affected is checked separately).
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:6536
.fetch_optional(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to lock recoverable execution intent: {e}"))?
.ok_or_else(|| anyhow::anyhow!("Execution intent {intent_id} was not found"))?;
anyhow::ensure!(
current_status == "prepared",
"Execution intent {intent_id} is {current_status}, not recoverable before signing"
);
let result = sqlx::query(
"
UPDATE execution_intent
SET status = 'recoverable', active = FALSE, updated_at = NOW()
WHERE id = $1 AND status = 'prepared'
",
)
.bind(intent_id)
.execute(&mut *transaction)
.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}"))?;
transactionView on GitHub (pinned to 18893faf8b)
Solutions
- Inspect the wrapped sqlx error source for the root cause (deadlock, connection closed, timeout) via the anyhow chain.
- Retry the whole mark_execution_intent_recoverable call with backoff — the failed transaction rolled back, so the state is unchanged and safe to redo.
- Check database health: connection limits, max_connections, statement_timeout, and recent deadlock logs.
- Reduce time spent holding the transaction open (avoid slow work between begin and commit) to lower deadlock/timeout risk.
Defensive patterns
Strategy: retry
Try / catch
// Retry transient DB failures with backoff; transaction rolled back so state is safe
for attempt in 0..3 {
match db.mark_execution_intent_recoverable(id).await {
Ok(()) => break,
Err(e) if attempt < 2 && is_transient_db_error(&e) => tokio::time::sleep(backoff(attempt)).await,
Err(e) => return Err(e),
}
} Prevention
- Keep pool settings (max_lifetime, idle_timeout) longer than the longest recovery transaction.
- Monitor deadlocks and set a sane statement_timeout.
- Avoid doing slow non-DB work while holding the transaction.
- Use connection retries at the pool/infra layer for failovers.
When it happens
Trigger: The `UPDATE execution_intent SET status='recoverable' ...` statement inside the open transaction fails — connection drop mid-transaction, deadlock on the FOR UPDATE-locked row, statement timeout, or constraint/permission failure.
Common situations: Database connection pool exhausted or idle-timeout closed the connection while the transaction was held open; another long transaction deadlocked on the intent row; Postgres restart/failover during recovery; network partition between the adapter and the database.
Related errors
- Error executing statement {sql_statement} with error: {e:?}
- Failed to set synchronous_commit ON: {e}
- Failed to activate execution schema version 2: {e}
- Failed to start execution verification migration: {e}
- Failed to install verification schema: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/eb7c2244f4886398.
Report an issue: GitHub.