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
- Resolve all pending/included/reverted rows in execution_transaction to a terminal status using the prior (version 1) software before upgrading.
- Shut down the old-version node cleanly and let it drain all in-flight transactions before starting the migration.
- Query SELECT COUNT(*) FROM execution_transaction WHERE status IN ('pending','included','reverted') beforehand and only run the migration when it returns 0.
- 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
- Drain all in-flight legacy transactions before scheduling a schema upgrade
- Stop the old-version writer before running migrations
- Pre-check pending transaction counts as part of the upgrade runbook
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
- Failed to install verification schema
- Execution payload rollback left
- Execution verification schema version
- Failed to activate execution schema version 2
- Failed to commit execution schema migration
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)