nautechsystems/nautilus_trader · error · anyhow::Error
Failed to mark execution intent replaced: {e}
Error message
Failed to mark execution intent replaced: {e} What it means
Wrapped sqlx error from the UPDATE execution_intent SET status = 'replaced', updated_at = NOW() WHERE id = $1 statement in add_execution_replacement_hash (database.rs:3842-3849). At this point the intent row is already locked FOR UPDATE and the replacement hash row was persisted, so a failure here rolls the whole replacement recording back. Typical causes are connection loss, timeouts, or a constraint on the status column rejecting 'replaced'.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:3848
",
)
.bind(intent_id)
.bind(chain_id_db)
.bind(transaction_hash)
.fetch_optional(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to persist replacement hash {transaction_hash}: {e}"))?
.ok_or_else(|| {
anyhow::anyhow!("Replacement hash {transaction_hash} conflicts with another intent")
})?;
sqlx::query(
"UPDATE execution_intent SET status = 'replaced', updated_at = NOW() WHERE id = $1",
)
.bind(intent_id)
.execute(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to mark execution intent replaced: {e}"))?;
sqlx::query(
"
INSERT INTO execution_transaction_transition (
intent_id, transaction_hash_id, transition_key, from_status, to_status
) VALUES ($1, $2, $3, $4, 'replaced')
ON CONFLICT (intent_id, transition_key) DO NOTHING
",
)
.bind(intent_id)
.bind(row.id)
.bind(format!("replaced:{transaction_hash}"))
.bind(current_status)
.execute(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to record replacement transition: {e}"))?;
transaction
.commit()View on GitHub (pinned to 2114cf6f76)
Solutions
- Downcast to sqlx::Error to distinguish constraint faults from connectivity faults
- Widen any status CHECK constraint to include 'replaced' via a migration if that is the reported cause
- Retry the full add_execution_replacement_hash call on transient classes; the rollback makes the retry idempotent (the 'replaced:{hash}' transition key dedupes)
- Keep the transaction short to reduce the lock-held window and timeout exposure
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.add_execution_replacement_hash(intent_id, chain_id, &hash).await {
Ok(row) => Ok(row),
Err(e) if is_transient_db_error(&e) => retry_with_backoff(e),
Err(e) => Err(e),
} Prevention
- Keep status CHECK constraints in execution_intent in sync with the TransactionStatus set
- Apply replacement-support migrations before deploying code that records replacements
- Short transactions again: this statement runs late in the flow, so lock-held time is the risk
When it happens
Trigger: Connection dropping mid-transaction after the upsert; a CHECK/domain on execution_intent.status that does not include 'replaced' (schema older than replacement support); statement_timeout or serialization abort hitting at this statement.
Common situations: Schema migrated only partially when replacement support landed; Postgres failover during replacement processing; very tight statement_timeout with the FOR UPDATE lock held through several statements.
Related errors
- Failed to retire replaced execution hash: {e}
- Failed to update execution hash {transaction_hash}: {e}
- Failed to update execution intent {intent_id}: {e}
- Failed to record execution transition: {e}
- Failed to start replacement transaction persistence: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/d3ce414524233c4c.
Report an issue: GitHub.