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

  1. Do not blindly re-submit: the signed transaction may or may not have been persisted; treat the outcome as ambiguous
  2. 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)
  3. Check the execution_transaction_hash table for the tx_hash from the message to learn which side of the ambiguity you are on
  4. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/611f910c3371adde. Report an issue: GitHub.