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
- Check the execution_intents table for the intent_id to confirm it exists and is still in the prepared/unassigned state.
- Ensure the prepare step (INSERT of the intent) completed successfully before calling nonce assignment.
- Make assignment idempotent or add a state guard so retries with an already-assigned intent are detected and treated as success rather than error.
- 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
- Always complete the prepare/INSERT step before nonce assignment.
- Make assignment idempotent: detect prior successful assignment of the same nonce.
- Serialize assignments per intent_id to avoid concurrent writers.
- Watch for cleanup/retention jobs deleting or mutating intent rows mid-flight.
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
- Execution payload storage is not rolling back
- Invalid execution transition for intent
- A transaction is being prepared; at most one transaction…
- Active replacement scan intent was not found
- Canonical nonce ledger changed during finality transition
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)