nautechsystems/nautilus_trader · error · anyhow::Error

Execution intent is not prepared for nonce

Error message

Execution intent {intent_id} is not prepared for nonce {nonce}

What it means

This error is raised after the nonce-assignment UPDATE executes successfully but reports rows_affected() != 1, meaning no (or multiple) prepared execution intents matched the given intent_id. It is an integrity check: the intent must exist and be in the prepared state for the nonce to be bound. The invariant is enforced via anyhow::ensure! in database.rs:5875.

Solutions

  1. Check the execution_intents table for the intent_id to confirm it exists and is still in the prepared/unassigned state.
  2. Ensure the prepare step (INSERT of the intent) completed successfully before calling nonce assignment.
  3. Make assignment idempotent or add a state guard so retries with an already-assigned intent are detected and treated as success rather than error.
  4. Serialize nonce assignment per intent (DB-level row lock, SELECT ... FOR UPDATE, or application-side per-intent mutex) to prevent racing writers.

Example fix

// before: blind retry loops on this error
loop { database.assign_execution_intent_nonce(&intent_id, nonce).await?; }
// after: verify prepared state first, treat already-assigned as success
let row = sqlx::query!("SELECT nonce FROM execution_intents WHERE intent_id = $1", intent_id)
    .fetch_optional(&pool).await?;
match row {
    Some(r) if r.nonce == Some(nonce_db) => { /* already assigned, idempotent no-op */ }
    Some(r) if r.nonce.is_none() => database.assign_execution_intent_nonce(&intent_id, nonce).await?,
    _ => anyhow::bail!("intent {intent_id} not in assignable state"),
}
Defensive patterns

Strategy: validation

Validate before calling

// check the intent is present and still assignable before assignment
let assignable = sqlx::query(
    "SELECT 1 FROM execution_intents WHERE intent_id = $1 AND nonce IS NULL")
    .bind(intent_id).fetch_optional(&pool).await?.is_some();
if !assignable { return Err(anyhow::anyhow!("intent {intent_id} missing or already assigned")); }

Try / catch

// treat 'not prepared' as an idempotency signal, not a crash
match database.assign_execution_intent_nonce(&intent_id, nonce).await {
    Err(e) if e.to_string().contains("is not prepared for nonce") => {
        verify_existing_assignment(&pool, intent_id, nonce)?; // success if already assigned to same nonce
    }
    Ok(()) => {},
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the nonce-assignment method with an intent_id that was never prepared (INSERT missing or failed), was already assigned a nonce in a prior call, was deleted/expired between preparation and assignment, or whose row was modified by a concurrent writer so the UPDATE's WHERE clause matched zero rows.

Common situations: Double-submission of the same nonce assignment after a retry; calling assignment before the prepare/INSERT step completed; intent rows purged by a cleanup job; mismatched intent_id from a different chain or epoch; concurrent operators racing to assign the same intent.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/d1d62ffabdf72445. Report an issue: GitHub.

Appendix: source

Thrown at crates/adapters/blockchain/src/cache/database.rs:5875

        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(())
    }

    /// Assigns the canonical nonce and its authorizing verification evidence atomically.
    ///
    /// # Errors
    ///
    /// Returns an error if the durable nonce ledger, intent ownership, manifest identity, or
    /// evidence is inconsistent, or if persistence fails.
    pub(crate) async fn assign_execution_intent_nonce_verified(
        &self,
        assignment: &ExecutionNonceAssignment<'_>,
    ) -> anyhow::Result<()> {
        anyhow::ensure!(
            !assignment.decisions.is_empty(),

View on GitHub (pinned to 18893faf8b)