nautechsystems/nautilus_trader · error · anyhow::Error
Failed to persist transaction {tx_hash}: {e}; the in-flight
Error message
Failed to persist transaction {tx_hash}: {e}; the in-flight slot stays occupied What it means
After signing, the client persists the transaction hash via add_execution_transaction_hash and that Postgres write failed ({e} carries the DB error). The write happens before any broadcast, but because a cancelled future cannot know whether the row committed, the client deliberately leaves the in-flight slot occupied and returns this error; restart-time reconciliation resolves the intent from whatever actually committed.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:1547
intent_id: prepared.intent_id,
nonce: prepared.nonce,
tx_hash: prepared.tx_hash,
purpose,
}));
}
let tx_hash = prepared.tx_hash;
self.database
.add_execution_transaction_hash(
prepared.intent_id,
self.chain_id,
&tx_hash.to_string(),
&prepared.raw_tx,
)
.await
.map(|_| ())
.map_err(|e| {
anyhow::anyhow!(
"Failed to persist transaction {tx_hash}: {e}; the in-flight slot stays occupied"
)
})
}
/// Broadcasts the signed transaction and classifies the acceptance outcome.
async fn broadcast(&self, prepared: &PreparedTransaction) -> anyhow::Result<BroadcastOutcome> {
let tx_hash = prepared.tx_hash;
self.database
.record_execution_status(
prepared.intent_id,
&tx_hash.to_string(),
TransactionStatus::Broadcast,
None,
None,
None,
None,View on GitHub (pinned to 2114cf6f76)
Solutions
- Do not blindly re-submit: the signed transaction may or may not have been persisted; treat the outcome as ambiguous
- Restore database connectivity, then restart/reconnect the client so reconcile_unresolved_execution resolves the intent (it will pick up the signed hash or mark the intent recoverable)
- Check the execution_transaction_hash table for the tx_hash from the message to learn which side of the ambiguity you are on
- Harden Postgres (pool size, timeouts, HA) so single write failures do not interrupt the persist-before-broadcast path
Defensive patterns
Strategy: try-catch
Validate before calling
-- Before (re)starting, confirm DB reachability and table health SELECT 1; SELECT count(*) FROM execution_transaction_hash WHERE intent_id = :active_intent_id; -- A healthy store answers quickly; latency/failures here predict the persist error.
Try / catch
try:
await submit_and_wait(order)
except Exception as e:
if 'Failed to persist transaction' in str(e):
# AMBIGUOUS outcome: do NOT re-submit. Keep wallet/config/store identical,
# restore DB, then reconnect so reconcile_unresolved_execution resolves the intent.
alert_ops('execution persist failure - reconciliation required')
raise Prevention
- Treat this error as reconciliation-required, never as retry-eligible: a re-submission risks a duplicate spend
- Keep Postgres highly available and the connection pool generously sized relative to order throughput
- Always reconnect the same wallet + database after this failure so the occupied slot resolves
When it happens
Trigger: fill_and_persist while Postgres is unreachable (connection dropped, pool exhausted, timeout) or the insert violates a constraint; the error text explicitly notes the slot stays occupied to preserve the safety invariant.
Common situations: Database failover or restart mid-trade; Postgres connection pool sized too small for the trading loop; network partition between the trading host and the DB host.
Related errors
- Failed to persist broadcast attempt for transaction {tx_hash
- Execution intent {} uses unsupported schema version {}
- Execution intent {} has unknown purpose {}
- Active execution intent {} has no nonce
- Execution intent {intent_id} has no current hash
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/611f910c3371adde.
Report an issue: GitHub.