nautechsystems/nautilus_trader · error
Active terminal intent conflicts with the canonical nonce le
Error message
Active terminal intent conflicts with the canonical nonce ledger
What it means
This invariant fires during migration when an intent is simultaneously marked `active` (still occupying its nonce in the signer's ledger) and carries a terminal status (`finalized` or `reverted`), while its nonce equals `next_canonical_nonce` — the nonce the ledger is about to hand out. A finalized/reverted transaction has been mined and its nonce consumed, so it cannot still be the active occupant of the next canonical nonce. The library throws to prevent the nonce ledger from double-tracking a consumed nonce.
Source
Thrown at crates/adapters/blockchain/src/execution/client.rs:5467
});
continue;
}
let nonce = intent.nonce.ok_or_else(|| {
anyhow::anyhow!("Retained signed intent {} has no nonce", intent.id)
})?;
let current = current.ok_or_else(|| {
anyhow::anyhow!("Retained signed intent {} has no current hash", intent.id)
})?;
let raw_transaction = authenticated.get(¤t.id).ok_or_else(|| {
anyhow::anyhow!(
"Retained signed intent {} has no authenticated current payload",
intent.id
)
})?;
if intent.active && nonce == next_canonical_nonce {
anyhow::ensure!(
!matches!(intent.status.as_str(), "finalized" | "reverted"),
"Active terminal intent conflicts with the canonical nonce ledger"
);
records.push(ExecutionVerificationMigrationRecord {
intent_id: intent.id,
nonce: Some(nonce),
transaction_hash: Some(current.transaction_hash.clone()),
terminal_status: None,
block_number: None,
block_hash: None,
receipt_success: None,
gas_used: None,
effective_gas_price: None,
recover_prepared: false,
decisions: vec![base_decision],
});
continue;
}View on GitHub (pinned to 18893faf8b)
Solutions
- For the reported intent id, reconcile its `active` flag with its status: a finalized/reverted intent must have active=false in the state store, then restart migration.
- Restore from the last consistent state snapshot taken before the interrupted terminal update.
- Verify the transaction's actual on-chain state via its receipt and set the intent record to match reality (terminal, inactive) before re-running.
- Make terminal status updates write `status` and `active` atomically in a single transaction to prevent recurrence.
Example fix
// before (two separate writes; crash between them)
store.set_status(intent_id, "finalized");
store.set_active(intent_id, false); // never ran after crash
// after (atomic update)
store.update(intent_id, |i| { i.status = "finalized"; i.active = false; })?; Defensive patterns
Strategy: validation
Validate before calling
fn validate_intent_consistency(intent: &Intent) -> Result<(), String> {
let terminal = matches!(intent.status.as_str(), "finalized" | "reverted");
if terminal && intent.active {
return Err(format!("intent {} terminal but still active", intent.id));
}
Ok(())
} Type guard
fn is_consistent(intent: &Intent) -> bool {
!matches!(intent.status.as_str(), "finalized" | "reverted") || !intent.active
} Prevention
- Update status and active flag atomically in a single persistence transaction.
- Enable journaling/WAL on the state store to survive crashes mid-update.
- After any crash, run a reconciliation pass clearing active on terminal intents before migration.
- Avoid manual edits to status fields in the state store.
When it happens
Trigger: Raised by `anyhow::ensure!(!matches!(intent.status.as_str(), "finalized" | "reverted"), ...)` when a retained intent's status persisted as finalized/reverted but its `active` flag was not cleared, and its nonce matches the next canonical nonce — e.g. a crash between marking the intent terminal and deactivating it, or inconsistent updates that wrote status without flipping active.
Common situations: Node crash mid-terminal-update; a state-store write that is not atomic across the status and active fields; manual state edits that set status without clearing active; replaying old WAL entries in the wrong order after a restore.
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 transaction {} unexpectedly retains signed bytes
- Finalized transaction {} emitted {} Swap logs from expected
- Finalized Swap input {base_amount} does not match the persis
- Finalized Swap has no calculated trade information
- Failed to load active execution intent: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/5b9e862d9f806d69.
Report an issue: GitHub.