nautechsystems/nautilus_trader · error · anyhow::Error

Execution intent {intent_id} has more than one current hash

Error message

Execution intent {intent_id} has more than one current hash

What it means

current_execution_hash() found two or more rows with current = TRUE among an intent's transaction hashes, violating the single-current invariant maintained through replace operations (a replacement tx supersedes the previous one). With multiple candidates the reconciler cannot decide which hash to track, so it refuses rather than guessing.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:1833

            "Canonical head changed during signer-nonce replacement scan"
        );
        Ok(found)
    }

    fn release_slot(&self) {
        *self.in_flight.lock().expect("in-flight mutex poisoned") = None;
    }
}

fn current_execution_hash(
    intent_id: i64,
    hashes: &[ExecutionTransactionHashRow],
) -> anyhow::Result<&ExecutionTransactionHashRow> {
    let mut current = hashes.iter().filter(|row| row.current);
    let row = current
        .next()
        .ok_or_else(|| anyhow::anyhow!("Execution intent {intent_id} has no current hash"))?;
    anyhow::ensure!(
        current.next().is_none(),
        "Execution intent {intent_id} has more than one current hash"
    );
    Ok(row)
}

/// Polls for the receipt of a broadcast transaction until it exists or the poll bound
/// is exhausted. A `null` receipt result is a legitimate pending response.
#[cfg(test)]
async fn poll_for_receipt(
    http_rpc_client: &BlockchainHttpRpcClient,
    tx_hash: &B256,
    max_polls: u32,
    interval: Duration,
) -> anyhow::Result<Option<RpcTransactionReceipt>> {
    let mut last_error = None;
    let mut observed_pending = false;

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. List the duplicates: SELECT id, transaction_hash, status, current FROM execution_transaction_hash WHERE intent_id = <id> AND current ORDER BY id;
  2. Verify on-chain which transaction is canonical (latest with the intent's nonce) and set current = FALSE on the others
  3. Ensure only one NautilusTrader client instance per wallet+database runs at a time
  4. Re-run reconciliation after the repair

Example fix

-- before: two rows flagged current
-- id=101 current=t, id=105 current=t

-- after: keep only the on-chain-confirmed latest hash as current
UPDATE execution_transaction_hash
SET current = (id = 105)
WHERE intent_id = 42;
Defensive patterns

Strategy: try-catch

Validate before calling

-- Detect duplicate current flags before they block reconciliation
SELECT intent_id, count(*) AS current_rows
FROM execution_transaction_hash
WHERE current
GROUP BY intent_id
HAVING count(*) > 1;
-- Expect zero rows on a healthy store.

Try / catch

try:
    client.connect()
except Exception as e:
    if 'more than one current hash' in str(e):
        # verify which tx is canonical on-chain (intent nonce), keep only that row current,
        # set current = FALSE on the others, then reconnect
        ...
    raise

Prevention

When it happens

Trigger: Reconciliation loading an intent whose execution_transaction_hash rows contain several current = TRUE entries: concurrent writers racing on the flag update, a failed transaction around the supersede step, or manual SQL leaving extra rows flagged.

Common situations: Two client instances sharing one database/wallet (unsupported); crash between clearing the old flag and committing the new one wrapped in separate transactions; hand-run UPDATE statements during incident recovery.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/97fcb86a539ef9e2. Report an issue: GitHub.