{"record":{"id":"1ce9e5b50ec43964","repo":"nautechsystems/nautilus_trader","slug":"failed-to-commit-recoverable-transition-e","errorCode":null,"errorMessage":"Failed to commit recoverable transition: {e}","messagePattern":"Failed to commit recoverable transition: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":6557,"sourceCode":"            \"Execution intent {intent_id} is not recoverable from preparation\"\n        );\n        sqlx::query(\n            \"\n            INSERT INTO execution_transaction_transition (\n                intent_id, transition_key, from_status, to_status\n            ) VALUES ($1, 'recoverable', $2, 'recoverable')\n            ON CONFLICT (intent_id, transition_key) DO NOTHING\n            \",\n        )\n        .bind(intent_id)\n        .bind(current_status)\n        .execute(&mut *transaction)\n        .await\n        .map_err(|e| anyhow::anyhow!(\"Failed to record recoverable transition: {e}\"))?;\n        transaction\n            .commit()\n            .await\n            .map_err(|e| anyhow::anyhow!(\"Failed to commit recoverable transition: {e}\"))?;\n        Ok(())\n    }\n\n    /// Persists a signed transaction and advances its intent before broadcast.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if the intent is not prepared, lacks a nonce, conflicts with a stored\n    /// hash, or persistence fails.\n    pub async fn add_execution_transaction_hash(\n        &self,\n        intent_id: i64,\n        chain_id: u32,\n        transaction_hash: &str,\n        raw_transaction: &[u8],\n    ) -> anyhow::Result<ExecutionTransactionHashRow> {\n        self.add_execution_transaction_payload(\n            intent_id,","sourceCodeStart":6539,"sourceCodeEnd":6575,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L6539-L6575","documentation":"`mark_execution_intent_recoverable` opens a PostgreSQL transaction, locks the execution intent, marks it 'recoverable', records a transition row, then commits. This error wraps a failure of the final `transaction.commit().await` call, meaning all in-transaction work (intent status change and transition insert) was rolled back by the database.","triggerScenarios":"The COMMIT statement itself fails after the UPDATE/INSERT succeeded inside the transaction — typically a lost or dropped database connection, connection pool timeout, deadlock detection aborting the transaction, serialization failure under repeatable-read, or the database shutting down mid-commit.","commonSituations":"Database restarted or failed over during the operation; idle connection in the pool was terminated by a firewall/load balancer before commit; statement_timeout or lock timeout hit on the FOR UPDATE row lock; running a migration that locks `execution_intent` concurrently, causing the commit/lock wait to abort.","solutions":["Check database connectivity and pool health (sqlx pool options: acquire_timeout, max_lifetime, idle_timeout) and retry `mark_execution_intent_recoverable`; the operation is idempotent-safe to retry since the transaction rolled back","Inspect Postgres logs for the matching commit failure (deadlock, serialization failure 40001, connection reset) and address the root cause","Shorten transaction scope / ensure no long-held locks on the `execution_intent` row (e.g. concurrent migrations or other FOR UPDATE holders)","Add retry-with-backoff around transient SQLSTATE classes (08000 connection exceptions, 40001 serialization failures)"],"exampleFix":"// before\ntransaction.commit().await\n    .map_err(|e| anyhow::anyhow!(\"Failed to commit recoverable transition: {e}\"))?;\n// after\ntransaction.commit().await\n    .map_err(|e| anyhow::anyhow!(\"Failed to commit recoverable transition: {e}\"))\n    .inspect_err(|e| tracing::error!(intent_id, \"commit failed, intent remains 'prepared': {e:#}\"))?;","handlingStrategy":"retry","validationCode":"// Check DB reachability before the call\nlet healthy = sqlx::query(\"SELECT 1\").execute(&db.pool).await.is_ok();\nanyhow::ensure!(healthy, \"database unreachable, defer recoverable transition\");","typeGuard":null,"tryCatchPattern":"match db.mark_execution_intent_recoverable(intent_id).await {\n    Ok(()) => {}\n    Err(e) if is_transient_sqlstate(&e) => schedule_retry(intent_id, e),\n    Err(e) => return Err(e),\n}\n\nfn is_transient_sqlstate(e: &anyhow::Error) -> bool {\n    let msg = format!(\"{e:#}\");\n    [\"08000\", \"40001\", \"connection\", \"closed\"].iter().any(|s| msg.contains(s))\n}","preventionTips":["Tune sqlx pool settings (acquire_timeout, max_lifetime, idle_timeout) to survive firewalls/load balancers that kill idle connections","Retry commit failures with backoff — the transaction rolls back atomically, so retrying is safe","Avoid running schema migrations concurrently with intent state transitions","Monitor Postgres logs for deadlocks and lock timeouts on execution_intent"],"tags":["database","sqlx","transaction-commit","persistence"],"backgroundTag":"database-write-failed","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}