nautechsystems/nautilus_trader · error
Execution intent {intent_id} is not recoverable from prepara
Error message
Execution intent {intent_id} is not recoverable from preparation What it means
The conditional UPDATE (`WHERE id = $1 AND status = 'prepared'`) affected 0 rows even though the earlier SELECT FOR UPDATE read the row, so anyhow::ensure! raises "... is not recoverable from preparation". Since the row is FOR UPDATE-locked inside the same transaction, status should not have changed; 0 rows usually signals a logic/data anomaly rather than a race.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:6537
.await
.map_err(|e| anyhow::anyhow!("Failed to lock recoverable execution intent: {e}"))?
.ok_or_else(|| anyhow::anyhow!("Execution intent {intent_id} was not found"))?;
anyhow::ensure!(
current_status == "prepared",
"Execution intent {intent_id} is {current_status}, not recoverable before signing"
);
let result = sqlx::query(
"
UPDATE execution_intent
SET status = 'recoverable', active = FALSE, updated_at = NOW()
WHERE id = $1 AND status = 'prepared'
",
)
.bind(intent_id)
.execute(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to mark execution intent recoverable: {e}"))?;
anyhow::ensure!(
result.rows_affected() == 1,
"Execution intent {intent_id} is not recoverable from preparation"
);
sqlx::query(
"
INSERT INTO execution_transaction_transition (
intent_id, transition_key, from_status, to_status
) VALUES ($1, 'recoverable', $2, 'recoverable')
ON CONFLICT (intent_id, transition_key) DO NOTHING
",
)
.bind(intent_id)
.bind(current_status)
.execute(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to record recoverable transition: {e}"))?;
transaction
.commit()View on GitHub (pinned to 18893faf8b)
Solutions
- Inspect the row directly: SELECT id, status FROM execution_intent WHERE id = $1; confirm the status value matches 'prepared' byte-for-byte (casing, whitespace).
- Check for triggers or row-level security policies on execution_intent that could alter visibility or the status between SELECT and UPDATE.
- Verify no schema drift: the code expects a plain lowercase 'prepared' string in the status column.
- If it persists, treat as an internal invariant violation and file a bug with the transition audit log from execution_transaction_transition.
Defensive patterns
Strategy: try-catch
Validate before calling
let status: String = sqlx::query_scalar("SELECT status FROM execution_intent WHERE id = $1")
.bind(intent_id)
.fetch_one(pool)
.await?;
assert_eq!(status, "prepared", "unexpected status encoding: {status:?}"); Try / catch
// Should be impossible under normal operation; log loudly and halt recovery
match db.mark_execution_intent_recoverable(id).await {
Err(e) if e.to_string().contains("not recoverable from preparation") => {
tracing::error!(%id, "invariant violation: locked row did not update");
// escalate / page, inspect triggers and schema
}
other => other?,
} Prevention
- Ensure status values are stored as exact lowercase strings ('prepared').
- Audit for triggers or RLS policies on execution_intent that mutate or filter rows.
- Pin schema migrations to adapter expectations; verify with integration tests.
- Alert on this error — it indicates data or logic corruption, not transient failure.
When it happens
Trigger: Calling mark_execution_intent_recoverable on an intent whose UPDATE matched no row — in practice only possible via data corruption, a modified/inconsistent schema (e.g. status column collation or case mismatch), triggers altering the row, or reading through a different snapshot/replica setup than the UPDATE writes to.
Common situations: A database trigger or middleware rewrote the status during the transaction; schema drift (status stored with different casing such as 'PREPARED'); someone patched the UPDATE predicate; exotic replication routing where the lock and update hit divergent views.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Account {account_id} not found after cache update
- Error executing statement {sql_statement} with error: {e:?}
- Failed to activate execution schema version 2: {e}
- Failed to install verification schema: {e}
- Failed to read verification schema version: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/e9c9bbcfa4d89848.
Report an issue: GitHub.