nautechsystems/nautilus_trader · error · anyhow::Error
Failed to record replacement transition: {e}
Error message
Failed to record replacement transition: {e} What it means
Wrapped sqlx error from the INSERT INTO execution_transaction_transition (intent_id, transaction_hash_id, transition_key, from_status, to_status) VALUES (..., 'replaced') ON CONFLICT (intent_id, transition_key) DO NOTHING statement in add_execution_replacement_hash (database.rs:3851-3863). It appends the audit record for the replacement transition; the ON CONFLICT key is 'replaced:{transaction_hash}', so replaying the same replacement never duplicates. The error is a database-level fault, not a duplicate-key issue.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:3863
.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()
.await
.map_err(|e| anyhow::anyhow!("Failed to commit replacement transaction: {e}"))?;
Ok(row)
}
/// Marks one order event as emitted after dispatch.
///
/// # Errors
///
/// Returns an error if the event kind is unknown, the intent is absent, the opposing
/// terminal marker is already set, or persistence fails.
pub async fn mark_execution_event_emitted(
&self,
intent_id: i64,
event: &str,View on GitHub (pinned to 2114cf6f76)
Solutions
- Downcast and inspect the database error for FK or constraint details
- Ensure the transition table exists with its (intent_id, transition_key) unique index by running migrations
- Retry the whole call for transient causes - the rollback plus conflict key keep it safe
- Avoid manually deleting execution_transaction_hash rows for intents that may still receive replacement events
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
- Never hand-delete execution_transaction_hash or transition rows for live intents
- Keep the (intent_id, transition_key) unique index in place so DO NOTHING deduplication works
- Ship transition-table migrations with the code that writes 'replaced' transitions
When it happens
Trigger: Connection loss or timeout during the insert; a foreign-key violation on transaction_hash_id or intent_id (should not occur in-transaction unless rows were concurrently deleted); schema drift on the transition table columns.
Common situations: Postgres restart between the intent status update and this insert; migrations creating execution_transaction_transition not applied; manual cleanup deleting hash rows while a replacement is recorded.
Related errors
- Failed to record execution transition: {e}
- Failed to update execution hash {transaction_hash}: {e}
- Failed to update execution intent {intent_id}: {e}
- Failed to start replacement transaction persistence: {e}
- Failed to retire replaced execution hash: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/52fee280e482f9dc.
Report an issue: GitHub.