nautechsystems/nautilus_trader · error · anyhow::Error
Execution intent {intent_id} is {current_status}, not prepar
Error message
Execution intent {intent_id} is {current_status}, not prepared for signing What it means
The execution intent exists but its status is neither 'prepared' nor 'signed', so persisting a signed transaction is an invalid state transition. The library enforces a strict state machine: only prepared (or idempotently re-signed) intents may accept a signed payload.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:6660
)
.bind(EXECUTION_PAYLOAD_COMPONENT)
.fetch_one(&mut *transaction)
.await
.context("failed to inspect execution payload marker")?;
anyhow::ensure!(
!marker,
"Plaintext signed transaction persistence is disabled after payload activation"
);
}
let current_status = sqlx::query_scalar::<_, String>(
"SELECT status FROM execution_intent WHERE id = $1 FOR UPDATE",
)
.bind(intent_id)
.fetch_optional(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to lock execution intent {intent_id}: {e}"))?
.ok_or_else(|| anyhow::anyhow!("Execution intent {intent_id} was not found"))?;
anyhow::ensure!(
current_status == TransactionStatus::Prepared.as_str()
|| current_status == TransactionStatus::Signed.as_str(),
"Execution intent {intent_id} is {current_status}, not prepared for signing"
);
let row = sqlx::query_as::<_, ExecutionTransactionHashRow>(
"
INSERT INTO execution_transaction_hash (
intent_id, chain_id, transaction_hash, payload_expected,
raw_transaction, sealed_transaction, status
)
SELECT id, chain_id, $3, TRUE, $4, $5, 'signed'
FROM execution_intent
WHERE id = $1 AND chain_id = $2 AND nonce IS NOT NULL
ON CONFLICT (chain_id, transaction_hash) DO UPDATE
SET transaction_hash = EXCLUDED.transaction_hash
WHERE execution_transaction_hash.intent_id = EXCLUDED.intent_id
AND execution_transaction_hash.payload_expectedView on GitHub (pinned to 18893faf8b)
Solutions
- Fetch the intent's current status first and skip persistence if it is already past 'signed' (the work is done).
- If the status is a terminal/failure state, create a new intent with a fresh nonce instead of re-signing.
- Resolve concurrent workers (single-owner queue) so only one actor advances an intent.
- If status is stale due to a crashed prior run, reconcile the intent via the observation/recovery path rather than forcing a signed persist.
Example fix
// before
db.add_execution_transaction(intent_id, ...).await?;
// after
let status = db.intent_status(intent_id).await?;
if status != "prepared" && status != "signed" { return Ok(()); /* already advanced */ }
db.add_execution_transaction(intent_id, ...).await?; Defensive patterns
Strategy: validation
Validate before calling
let status: String = sqlx::query_scalar("SELECT status FROM execution_intent WHERE id=$1").bind(intent_id).fetch_one(&pool).await?;
if !(status == "prepared" || status == "signed") {
tracing::info!(%intent_id, %status, "intent already advanced; skipping signed persist");
return Ok(SkipReason::AlreadyAdvanced);
} Type guard
fn signable_status(status: &str) -> bool { status == "prepared" || status == "signed" } Try / catch
match db.add_execution_transaction(intent_id, ...).await {
Err(e) if e.to_string().contains("not prepared for signing") => {
let status = fetch_status(intent_id).await?;
info!(%status, "idempotent skip: intent already advanced");
}
r => r?,
} Prevention
- Check intent status before re-running any signing job after a crash
- Make signing consumers idempotent: treat 'not prepared for signing' as skip-if-done
- Ensure only one worker owns an intent at a time
- Track status transitions and alert on unexpected regressions
When it happens
Trigger: Calling add_execution_transaction on an intent whose status is e.g. 'broadcast', 'confirmed', 'failed', or 'expired'. Typically a duplicate signing attempt after the transaction was already broadcast, a replay of an old signing job, or a concurrent worker that advanced the status between fetch and persist.
Common situations: Re-running a signing batch after a crash where intents already advanced; two consumers processing the same intent queue message; retrying a job that previously succeeded and moved the intent to a later status.
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
- Invalid execution transition for intent {intent_id}: {curren
- Invalid state trigger {self} -> {trigger}
- Execution payload storage is not rolling back
- Execution intent {intent_id} is not prepared for nonce {nonc
- Replacement scan conflicts with the canonical nonce or manif
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/dd1ed01e412100ea.
Report an issue: GitHub.