nautechsystems/nautilus_trader · error

Intent cannot attach a verified replacement from status {cur

Error message

Intent cannot attach a verified replacement from status {current_status}

What it means

When the scan found a replacement transaction hash, the intent's current status must permit the transition to `Replaced` per `execution_transition_allowed`. If the intent is in a status from which `Replaced` is illegal (e.g. already `replaced`, `failed`, or beyond the attachable lifecycle stage), the evidence cannot be attached and the transaction fails with the offending status embedded in the message.

Source

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

            .bind(nonce)
            .bind(decision.read_class)
            .bind(height_start)
            .bind(height_end)
            .bind(scan.manifest_version)
            .bind(scan.manifest_digest)
            .bind(scan.provider_ids)
            .bind(scan.operator_ids)
            .bind(scan.failure_domain_ids)
            .bind(&decision.normalized_value_digest)
            .bind(revision)
            .bind(transition_key)
            .execute(&mut *transaction)
            .await
            .context("failed to persist replacement scan evidence")?;
        }

        if let Some(transaction_hash) = scan.matched_transaction_hash {
            anyhow::ensure!(
                execution_transition_allowed(&current_status, TransactionStatus::Replaced),
                "Intent cannot attach a verified replacement from status {current_status}"
            );
            let (hash_id, payload_expected, already_current) =
                sqlx::query_as::<_, (i64, bool, bool)>(
                    "
                    SELECT id, payload_expected, current
                    FROM execution_transaction_hash
                    WHERE intent_id = $1 AND chain_id = $2 AND transaction_hash = $3
                    FOR UPDATE
                    ",
                )
                .bind(scan.intent_id)
                .bind(chain_id)
                .bind(transaction_hash)
                .fetch_optional(&mut *transaction)
                .await
                .context("failed to lock authenticated replacement payload")?

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the intent's current status before attaching a replacement; skip the attach step if it already equals `replaced`.
  2. Do not attach a replacement hash to intents in terminal states; record the finding elsewhere or open a new intent.
  3. Ensure scan steps run in the lifecycle order the state machine expects.

Example fix

// before
if let Some(tx_hash) = scan.matched_transaction_hash { db.record_execution_replacement_scan(&scan).await?; }
// after
if let Some(_) = scan.matched_transaction_hash {
    if db.execution_intent_status(scan.intent_id).await? == "replaced" { return Ok(()); } // already handled
}
db.record_execution_replacement_scan(&scan).await?;
Defensive patterns

Strategy: validation

Validate before calling

let status = sqlx::query_scalar::<_, String>("SELECT status FROM execution_intent WHERE id=$1").bind(scan.intent_id).fetch_optional(&pool).await?;
if let Some(s) = status {
    if s == "replaced" { return Ok(()); } // already transitioned
    if !matches!(s.as_str(), "prepared" | "pending") { return Err(anyhow!("status {s} cannot attach a replacement")); }
}

Type guard

fn can_attach_replacement(status: &str) -> bool { matches!(status, "prepared" | "pending") }

Try / catch

match db.record_execution_replacement_scan(&scan).await {
    Err(e) if e.to_string().starts_with("Intent cannot attach a verified replacement from status") => warn!("intent already advanced past attachable stage; skipping"),
    Err(e) => return Err(e),
    Ok(()) => {},
}

Prevention

When it happens

Trigger: Calling `record_execution_replacement_scan` with `matched_transaction_hash = Some(..)` while the locked intent's `status` is one for which `execution_transition_allowed(current_status, TransactionStatus::Replaced)` returns false — e.g. an already-`replaced` intent receiving a second replacement, or a `failed`/terminal-state intent.

Common situations: A duplicate scan re-attached a replacement after the first already transitioned the intent; the intent failed for another reason between scan start and commit; lifecycle stages were run out of order by the caller.

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/19007054cd14d367. Report an issue: GitHub.