{"record":{"id":"facf955c4568f13a","repo":"clockworklabs/SpacetimeDB","slug":"should-have-a-pending-mutable-anon-tx-as-procedur","errorCode":null,"errorMessage":"should have a pending mutable anon tx as `procedure_start_mut_tx` preceded","messagePattern":"should have a pending mutable anon tx as `procedure_start_mut_tx` preceded","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/bindings/src/lib.rs","lineNumber":1200,"sourceCode":"impl Deref for TxContext {\n    type Target = ReducerContext;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\n/// We need to passthrough identity and connection_id because procedures can be invoked by users.\n/// For [HttpContext] this is always anonymous ([Identity::ZERO]).\n/// Construct the inner [ReducerContext] with the appropriate caller information.\nfn try_with_tx<T, E>(\n    body: impl Fn(&TxContext) -> Result<T, E>,\n    identity: Identity,\n    connection_id: Option<ConnectionId>,\n) -> Result<T, E> {\n    let abort = || {\n        crate::sys::procedure::procedure_abort_mut_tx()\n            .expect(\"should have a pending mutable anon tx as `procedure_start_mut_tx` preceded\")\n    };\n\n    let run = || {\n        let timestamp = crate::sys::procedure::procedure_start_mut_tx()\n            .expect(\"holding `&mut HandlerContext`, so should not be in a tx already; called manually elsewhere?\");\n        let timestamp = Timestamp::from_micros_since_unix_epoch(timestamp);\n\n        let tx = ReducerContext::new(crate::Local {}, identity, connection_id, timestamp);\n        let tx = TxContext(tx);\n\n        struct DoOnDrop<F: Fn()>(F);\n        impl<F: Fn()> Drop for DoOnDrop<F> {\n            fn drop(&mut self) {\n                (self.0)();\n            }\n        }\n        let abort_guard = DoOnDrop(abort);\n        let res = body(&tx);","sourceCodeStart":1182,"sourceCodeEnd":1218,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings/src/lib.rs#L1182-L1218","documentation":"try_with_tx runs a procedure body inside an anonymous mutable transaction: procedure_start_mut_tx begins it and procedure_abort_mut_tx rolls it back on failure. This .expect fires on the abort path when the host answers that no transaction is pending - bindings and host disagree about transaction state (the start never happened or the transaction already ended). It is an internal invariant/ABI violation, not an application-level condition.","triggerScenarios":"Guest bindings built against a host with a different procedure-tx ABI; invoking procedure_start_mut_tx / procedure_abort_mut_tx manually so the start/abort pairing breaks; a host error path that already finalized the transaction before the bindings abort it.","commonSituations":"Mixing module and node versions after a partial upgrade; hand-rolled syscall wrappers in tests; a regression in a bindings release.","solutions":["Rebuild and redeploy the module with bindings that match the running host version.","Never call the procedure transaction syscalls directly; use the provided context wrappers only.","If versions match, report the bug with both versions and a minimal reproduction."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Panic, not Err: isolate at the host/embedding boundary.\nlet outcome = std::panic::catch_unwind(std::AssertUnwindSafe(|| invoke_procedure()));\nif outcome.is_err() {\n    // ABI state is now suspect: log versions of module and host, restart the\n    // module instance, and redeploy with matched bindings.\n}","preventionTips":["Build modules with the same bindings version as the host node.","Never call procedure_start_mut_tx / procedure_abort_mut_tx manually.","Pin versions in CI so host and guest artifacts upgrade together."],"tags":["panic","abi","transaction","wasm","rust","spacetimedb"],"backgroundTag":"host-guest-abi-mismatch","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}