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(¤t_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
- Check the intent's current status before attaching a replacement; skip the attach step if it already equals `replaced`.
- Do not attach a replacement hash to intents in terminal states; record the finding elsewhere or open a new intent.
- 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
- Read the intent status immediately before submitting a scan with a matched replacement.
- Idempotently skip scans for intents already in the `replaced` state.
- Keep the intent lifecycle single-threaded per intent so intermediate states cannot be missed.
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
- Replacement scan conflicts with the canonical nonce or manif
- Replacement scan cursor regressed or changed
- Verified finalized transaction count advanced without an act
- Verified finalized transaction count is outside the owned re
- Verified finalized header extension does not start at the du
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/19007054cd14d367.
Report an issue: GitHub.