nautechsystems/nautilus_trader · error · anyhow::Error

Cannot safely migrate {unresolved_legacy} unresolved executi

Error message

Cannot safely migrate {unresolved_legacy} unresolved execution schema version 1 transaction(s); resolve them with the prior version before enabling version {EXECUTION_SCHEMA_VERSION}

What it means

A deliberate fail-closed guard in ensure_execution_transaction_schema: the migration refuses to activate schema v2 while any legacy execution_transaction row is still in status pending, included, or reverted. Those rows are v1 executions whose outcome is not settled from the v2 signer-ownership model's perspective; migrating past them would strand them behind the execution_transaction_v2_fence trigger that rejects legacy writers.

Source

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

            && installed_version > EXECUTION_SCHEMA_VERSION
        {
            anyhow::bail!(
                "Execution schema version {} is newer than supported version {EXECUTION_SCHEMA_VERSION}",
                installed_version
            );
        }

        let unresolved_legacy = sqlx::query_scalar::<_, i64>(
            "
            SELECT COUNT(*)
            FROM execution_transaction
            WHERE status IN ('pending', 'included', 'reverted')
            ",
        )
        .fetch_one(&mut *transaction)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to inspect legacy execution transactions: {e}"))?;
        anyhow::ensure!(
            unresolved_legacy == 0,
            "Cannot safely migrate {unresolved_legacy} unresolved execution schema version 1 transaction(s); resolve them with the prior version before enabling version {EXECUTION_SCHEMA_VERSION}"
        );

        for statement in [
            "
            CREATE OR REPLACE FUNCTION execution_transaction_v2_fence()
            RETURNS TRIGGER AS $$
            BEGIN
                RAISE EXCEPTION 'Legacy execution writer refused after schema version 2 activation';
            END;
            $$ LANGUAGE plpgsql
            ",
            "DROP TRIGGER IF EXISTS execution_transaction_v2_fence ON execution_transaction",
            "
            CREATE TRIGGER execution_transaction_v2_fence
            BEFORE INSERT OR UPDATE OR DELETE ON execution_transaction
            FOR EACH STATEMENT EXECUTE FUNCTION execution_transaction_v2_fence()

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Start the previous binary version and let it drive the outstanding transactions to a settled state, then re-run the migration
  2. Where on-chain state confirms settlement, move the legacy rows out of pending/included/reverted with a reviewed manual UPDATE, then re-run
  3. Delete legacy rows that belong to discarded test wallets after verifying nothing references them
Defensive patterns

Strategy: validation

Validate before calling

-- Run before upgrading; a non-zero count means the migration will refuse
SELECT status, COUNT(*) FROM execution_transaction
WHERE status IN ('pending', 'included', 'reverted')
GROUP BY status;

Try / catch

match db.ensure_execution_transaction_schema().await {
    Err(e) if e.to_string().contains("Cannot safely migrate") => {
        // drain or settle legacy rows with the prior binary, then retry the upgrade
    }
    other => other?,
}

Prevention

When it happens

Trigger: Upgrading the binary while a v1 transaction is still pending; a v1 transaction stuck in included or reverted that never reached a fully settled state; a previous crash left pending rows behind.

Common situations: Node killed mid-execution and then upgraded; upgrading without draining in-flight transactions; stale rows from test wallets in a shared database.

Related errors


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