nautechsystems/nautilus_trader · error · anyhow::Error
Failed to commit replacement transaction: {e}
Error message
Failed to commit replacement transaction: {e} What it means
Wrapped sqlx error when transaction.commit() fails at the end of add_execution_replacement_hash (database.rs:3865-3869). All statements (lock, retire, upsert, status update, transition insert) already executed, so the durable outcome is ambiguous: the replacement may or may not have been persisted. The idempotency key 'replaced:{transaction_hash}' in the transition table makes a plain replay of the same call the safe resolution.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:3868
"
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,
) -> anyhow::Result<()> {
let statement = match event {
"acknowledgement" => {
"UPDATE execution_intent SET acknowledgement_emitted = TRUE, updated_at = NOW() WHERE id = $1"
}View on GitHub (pinned to 2114cf6f76)
Solutions
- Retry add_execution_replacement_hash with identical arguments; ON CONFLICT clauses make the replay converge
- If repeated commits fail, verify connection stability (keepalives, PgBouncer pooling mode, Postgres uptime)
- Check the transition table afterward if you must know whether the first attempt committed: SELECT 1 FROM execution_transaction_transition WHERE intent_id = $1 AND transition_key = 'replaced:' || $2
- Keep the transaction short so the commit window is minimal
Defensive patterns
Strategy: retry
Type guard
fn is_commit_ambiguity(err: &anyhow::Error) -> bool {
matches!(err.downcast_ref::<sqlx::Error>(), Some(sqlx::Error::Io(_)) | None)
} Try / catch
match db.add_execution_replacement_hash(intent_id, chain_id, &hash).await {
Ok(row) => Ok(row),
Err(e) if is_commit_ambiguity(&e) => {
// outcome unknown: replay; 'replaced:{hash}' transition key dedupes if it committed
db.add_execution_replacement_hash(intent_id, chain_id, &hash).await
}
Err(e) => Err(e),
} Prevention
- Rely on the transition_key idempotency design: replays after commit ambiguity are the intended recovery
- Minimize the commit window by keeping transactions short and free of external awaits
- Investigate repeated commit failures as connection-layer instability, not application logic
When it happens
Trigger: Connection dying at commit; Postgres restart or failover; serialization failure surfacing at commit; pool returning a broken connection for the commit round-trip.
Common situations: Unstable network between the trading host and Postgres; failover events during bursts of replacement/reorg processing; connection reaper killing sessions held open by slow commit paths.
Related errors
- Failed to commit execution transition: {e}
- Failed to retire replaced execution hash: {e}
- Failed to persist replacement hash {transaction_hash}: {e}
- Replacement hash {transaction_hash} conflicts with another i
- Failed to mark execution intent replaced: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/88a08e851920d99e.
Report an issue: GitHub.