nautechsystems/nautilus_trader · error · anyhow::Error
Execution intent {intent_id} is not prepared for nonce {nonc
Error message
Execution intent {intent_id} is not prepared for nonce {nonce} What it means
The guarded UPDATE in assign_execution_intent_nonce matched zero rows: the intent is not in status 'prepared', or it already owns a different nonce (the nonce IS NULL OR nonce = $2 predicate failed). This is the API failing closed against binding two different nonces to one intent, which would desynchronize the signer's on-chain nonce sequence.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:3417
nonce: u64,
) -> anyhow::Result<()> {
let nonce_db = i64::try_from(nonce)
.with_context(|| format!("Execution nonce {nonce} exceeds PostgreSQL BIGINT"))?;
let result = sqlx::query(
"
UPDATE execution_intent
SET nonce = $2, updated_at = NOW()
WHERE id = $1
AND status = 'prepared'
AND (nonce IS NULL OR nonce = $2)
",
)
.bind(intent_id)
.bind(nonce_db)
.execute(&self.pool)
.await
.map_err(|e| anyhow::anyhow!("Failed to assign nonce {nonce} to execution intent: {e}"))?;
anyhow::ensure!(
result.rows_affected() == 1,
"Execution intent {intent_id} is not prepared for nonce {nonce}"
);
Ok(())
}
/// Releases an intent when no broadcast attempt can have occurred.
///
/// # Errors
///
/// Returns an error if the intent advanced to broadcast or persistence fails.
pub async fn mark_execution_intent_recoverable(&self, intent_id: i64) -> anyhow::Result<()> {
let mut transaction = self.pool.begin().await.map_err(|e| {
anyhow::anyhow!("Failed to start recoverable execution transition: {e}")
})?;
let current_status = sqlx::query_scalar::<_, String>(
"SELECT status FROM execution_intent WHERE id = $1 FOR UPDATE",
)View on GitHub (pinned to 2114cf6f76)
Solutions
- Load the intent first and verify status is 'prepared' and nonce is NULL or equal before assigning
- If a different nonce is already stored, stop and reconcile against the node's transaction count - do not force the UPDATE
- If status advanced past 'prepared', resume from the persisted state instead of re-assigning
- Pass the identical nonce on retries so the assignment stays idempotent
Example fix
// before: assign whatever nonce was just fetched
let nonce = provider.transaction_count(wallet).await?;
db.assign_execution_intent_nonce(intent_id, nonce).await?;
// after: keep the persisted nonce authoritative
let nonce = match row.nonce {
Some(stored) => stored as u64,
None => provider.transaction_count(wallet).await?,
};
db.assign_execution_intent_nonce(row.id, nonce).await?; Defensive patterns
Strategy: validation
Validate before calling
// Verify ownership preconditions before assigning
let row: Option<(String, Option<i64>)> = sqlx::query_as(
"SELECT status, nonce FROM execution_intent WHERE id = $1",
)
.bind(intent_id)
.fetch_optional(&pool)
.await?;
match row {
Some((status, nonce)) if status == "prepared" && nonce.map_or(true, |n| n == nonce_db) => {
// safe to assign
}
_ => { /* reconcile instead of assigning */ }
} Type guard
fn intent_accepts_nonce(row: &ExecutionIntentRow, nonce: u64) -> bool {
row.status.as_str() == "prepared"
&& row.nonce.map_or(true, |stored| stored == nonce as i64)
} Try / catch
match db.assign_execution_intent_nonce(intent_id, nonce).await {
Err(e) if e.to_string().contains("is not prepared for nonce") => {
// load the intent and reconcile: resume from its persisted nonce or halt for manual review
}
other => other?,
} Prevention
- Treat the persisted nonce as authoritative - never re-derive and force a different one after restarts
- Keep the ExecutionIntentRow from reserve_execution_intent and use its status/nonce for all subsequent decisions
- Halt execution on nonce mismatch and reconcile with the node's transaction count rather than overriding
When it happens
Trigger: Calling assign after the intent advanced to 'signed'; a crash-recovery flow re-derives a nonce from the node and assigns it to an intent that already stored one; passing the wrong intent_id.
Common situations: Restart between reserve and assign where nonce management computed a fresh value; concurrent executors targeting the same intent; drift between the node's transaction count and the persisted nonce.
Related errors
- Cannot safely migrate {unresolved_legacy} unresolved executi
- Failed to assign nonce {nonce} to execution intent: {e}
- Invalid execution transition for intent {intent_id}: {curren
- Failed to start execution schema migration: {e}
- Failed to lock legacy execution transactions: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/d1d62ffabdf72445.
Report an issue: GitHub.