nautechsystems/nautilus_trader · error · anyhow::Error

Cannot safely migrate

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

This error is raised by the execution schema migration in database.rs:3616 before the v1 -> v2 schema upgrade runs. The migration inspects the legacy execution_transaction table and refuses to proceed when transactions in the non-terminal statuses ('pending', 'included', 'reverted') remain, because v2 installs a fence trigger that permanently blocks writes to that legacy table. The library requires all legacy version-1 transactions to be resolved (moved to a terminal status) with the prior schema before the new schema is enabled.

Solutions

  1. Resolve all pending/included/reverted rows in execution_transaction to a terminal status using the prior (version 1) software before upgrading.
  2. Shut down the old-version node cleanly and let it drain all in-flight transactions before starting the migration.
  3. Query SELECT COUNT(*) FROM execution_transaction WHERE status IN ('pending','included','reverted') beforehand and only run the migration when it returns 0.
  4. If rows are stale/orphaned (e.g. from a decommissioned wallet), clean them up with the prior version rather than editing the table directly.

Example fix

-- before (migration fails)
SELECT COUNT(*) FROM execution_transaction WHERE status IN ('pending','included','reverted');
-- e.g. 3 rows

-- after (resolve them with v1 first, e.g. mark dropped/replaced via the prior version)
SELECT COUNT(*) FROM execution_transaction WHERE status IN ('pending','included','reverted');
-- 0 rows, then run the v2 migration
Defensive patterns

Strategy: validation

Validate before calling

let unresolved: i64 = sqlx::query_scalar(
    "SELECT COUNT(*) FROM execution_transaction WHERE status IN ('pending','included','reverted')",
).fetch_one(&pool).await?;
if unresolved != 0 {
    anyhow::bail!("{unresolved} unresolved legacy transactions; drain before v2 migration");
}

Try / catch

match migrate_execution_schema(&pool).await {
    Ok(()) => {},
    Err(e) if e.to_string().contains("unresolved execution schema version 1") => {
        // drain legacy transactions with v1, then retry
    },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the schema migration routine when SELECT COUNT(*) FROM execution_transaction WHERE status IN ('pending','included','reverted') returns a count greater than 0, i.e. unresolved legacy transactions exist at migration time.

Common situations: Upgrading a node to a build that activates EXECUTION_SCHEMA_VERSION 2 while the legacy writer still has in-flight pending/included/reverted rows; a crash or shutdown left legacy transactions unresolved; running the migration manually against a database that was actively written by the old version.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

            && 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 18893faf8b)