nautechsystems/nautilus_trader · error · anyhow::Error
Failed to lock migrated intent: {e}
Error message
Failed to lock migrated intent: {e} What it means
This error wraps any sqlx failure from `SELECT status FROM execution_intent WHERE id = $1 FOR UPDATE` during the verification migration that reconstructs terminal transaction state. It fires when the intent row cannot be locked/read inside the migration transaction — most often because the row does not exist (query_scalar expects exactly one row) or the SELECT fails (connection, aborted transaction, or SQL error). The underlying database error is interpolated into the message as {e}.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:4252
})?;
let gas_used = i64::try_from(record.gas_used.ok_or_else(|| {
anyhow::anyhow!("Terminal migration record has no gas usage")
})?)
.context("Migration gas usage exceeds PostgreSQL BIGINT")?;
let receipt_success = record.receipt_success.ok_or_else(|| {
anyhow::anyhow!("Terminal migration record has no receipt status")
})?;
anyhow::ensure!(
receipt_success == (status == TransactionStatus::Finalized),
"Migration receipt status conflicts with terminal status"
);
let current_status = sqlx::query_scalar::<_, String>(
"SELECT status FROM execution_intent WHERE id = $1 FOR UPDATE",
)
.bind(record.intent_id)
.fetch_one(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to lock migrated intent: {e}"))?;
let hash_result = sqlx::query(
"
UPDATE execution_transaction_hash
SET status = $3, block_number = $4, block_hash = $5,
receipt_success = $6, gas_used = $7,
effective_gas_price = $8, updated_at = NOW()
WHERE intent_id = $1 AND transaction_hash = $2 AND current
AND payload_expected
",
)
.bind(record.intent_id)
.bind(transaction_hash)
.bind(status.as_str())
.bind(block_number)
.bind(block_hash)
.bind(receipt_success)
.bind(gas_used)
.bind(record.effective_gas_price.as_deref())View on GitHub (pinned to 18893faf8b)
Solutions
- Check that every intent_id in the migration evidence exists in execution_intent before running the migration (SELECT id FROM execution_intent WHERE id = ANY($1)).
- Run the migration from a single worker/instance only, so FOR UPDATE locks cannot deadlock.
- Inspect the interpolated {e} in the log: RowNotFound means a missing intent row — fix the evidence or restore the row; 'current transaction is aborted' means an earlier statement in this transaction failed.
- Verify the target database has the expected schema (\d execution_intent) and that you are connected to the intended database.
- If the cause was a transient connection error, re-run the migration; the transaction rollback leaves state consistent.
Example fix
// before: migration assumes all evidence intent_ids exist
migrate_records(records).await?;
// after: pre-validate evidence against the DB
let missing = sqlx::query_scalar::<_, String>(
"SELECT id FROM unnest($1::text[]) AS t(id) WHERE id NOT IN (SELECT id FROM execution_intent)",
).bind(&intent_ids).fetch_all(&pool).await?;
anyhow::ensure!(missing.is_empty(), "evidence references missing intents: {missing:?}"); Defensive patterns
Strategy: validation
Validate before calling
let exists = sqlx::query_scalar::<_, i64>(
"SELECT COUNT(*) FROM execution_intent WHERE id = $1",
).bind(&record.intent_id).fetch_one(&pool).await?;
if exists == 0 {
anyhow::bail!("intent {} missing from execution_intent; fix evidence before migrating", record.intent_id);
} Type guard
fn migration_record_has_existing_intent(record: &MigrationRecord, intent_ids: &HashSet<String>) -> bool {
intent_ids.contains(&record.intent_id)
} Try / catch
match migrate_records(records).await {
Err(e) if e.to_string().contains("Failed to lock migrated intent") => {
tracing::error!("migration lock failed (missing row or concurrent migration): {e:#}");
// do not blindly retry: check evidence/DB first, then retry once
}
Err(e) => return Err(e),
Ok(()) => {}
} Prevention
- Pre-validate that every intent_id in migration evidence exists before starting the migration.
- Run the migration from a single worker/instance to avoid FOR UPDATE deadlocks.
- Run the migration inside a maintenance window with no concurrent writers.
- Check the interpolated underlying sqlx error: RowNotFound means a missing intent row.
When it happens
Trigger: Running the verification migration when (1) record.intent_id has no row in execution_intent (fetch_one on an empty result -> RowNotFound), (2) the migration transaction was already aborted by an earlier failed statement or hits a serialization/deadlock failure on the FOR UPDATE lock under concurrent access, (3) the DB connection drops mid-transaction, or (4) the execution_intent table is missing/renamed in the target database.
Common situations: Migrating evidence that references intents hard-deleted from the DB; running the migration concurrently from two workers causing deadlocks on the row lock; pointing the migration at a database with an older/missing schema; transient network blips to Postgres during a long migration batch.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
- Failed to reconstruct migrated hash: {e}
- Failed to reconstruct migrated intent: {e}
- Failed to persist migration evidence: {e}
- Failed to lock execution intent for nonce assignment: {e}
- Failed to seed chain table: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/328bb77ef4f89921.
Report an issue: GitHub.