clockworklabs/SpacetimeDB · critical

transaction retry failed again

Error message

transaction retry failed again

What it means

After the anonymous transaction's body ran, procedure_commit_mut_tx failed; the bindings then re-ran the body once and retried the commit. This .expect fires when that second commit also fails - the body deterministically reproduces a commit-breaking condition (a write conflict, a constraint violation, or an I/O error during commit).

Source

Thrown at crates/bindings/src/lib.rs:1230

        impl<F: Fn()> Drop for DoOnDrop<F> {
            fn drop(&mut self) {
                (self.0)();
            }
        }
        let abort_guard = DoOnDrop(abort);
        let res = body(&tx);
        core::mem::forget(abort_guard);
        res
    };

    let mut res = run();

    match res {
        Ok(_) if crate::sys::procedure::procedure_commit_mut_tx().is_err() => {
            log::warn!("committing anonymous transaction failed");
            res = run();
            match res {
                Ok(_) => crate::sys::procedure::procedure_commit_mut_tx().expect("transaction retry failed again"),
                Err(_) => abort(),
            }
        }
        Ok(_) => {}
        Err(_) => abort(),
    }

    res
}

fn with_tx<T>(body: impl Fn(&TxContext) -> T, identity: Identity, connection_id: Option<ConnectionId>) -> T {
    use core::convert::Infallible;
    match try_with_tx::<T, Infallible>(|tx| Ok(body(tx)), identity, connection_id) {
        Ok(v) => v,
        Err(e) => match e {},
    }
}

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Inspect host logs for the underlying commit error on both attempts.
  2. Make the body's writes idempotent and conflict-free: narrow row sets, avoid hot rows, use upsert-style writes.
  3. Reduce transaction size or split work across multiple commits.
  4. If host-side I/O is at fault, fix storage before retrying.
Defensive patterns

Strategy: try-catch

Try / catch

let outcome = std::panic::catch_unwind(std::AssertUnwindSafe(|| invoke_procedure()));
if outcome.is_err() {
    // Second commit failed too: pull the underlying commit error from host logs,
    // fix the conflict or storage issue, then re-invoke the procedure fresh.
}

Prevention

When it happens

Trigger: A body whose writes conflict with concurrent activity on both attempts (hot rows, contention); disk/IO errors at commit time; oversized transactions; state that is deterministically invalid on every run.

Common situations: High-contention tables causing repeated conflicts; failing storage during commit; bugs where the body always writes an invalid combination of rows.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/afcc53a8b27133f3. Report an issue: GitHub.