{"record":{"id":"a5ce5134304e4214","repo":"clockworklabs/SpacetimeDB","slug":"holding-mut-handlercontext-so-should-not-be-in","errorCode":null,"errorMessage":"holding `&mut HandlerContext`, so should not be in a tx already; called manually elsewhere?","messagePattern":"holding `&mut HandlerContext`, so should not be in a tx already; called manually elsewhere\\?","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/bindings/src/lib.rs","lineNumber":1205,"sourceCode":"    }\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);\n        core::mem::forget(abort_guard);\n        res\n    };\n\n    let mut res = run();","sourceCodeStart":1187,"sourceCodeEnd":1223,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings/src/lib.rs#L1187-L1223","documentation":"procedure_start_mut_tx failed because a transaction is already active for this call - try_with_tx was entered re-entrantly (a transactional procedure invoked while another transaction from the same context is still open). The .expect converts the host's refusal into a panic.","triggerScenarios":"Invoking a tx-wrapped procedure or helper from inside another reducer/procedure that already holds the anonymous mutable tx; calling procedure_start_mut_tx manually before a library wrapper runs; version skew between bindings and host.","commonSituations":"Refactoring so transactional helpers end up nested; scheduling side-effecting calls inside an open transaction; mixing old and new bindings.","solutions":["Restructure so transaction-scoped helpers are not nested; perform side effects after the outer transaction completes.","Remove manual syscall usage that opens a transaction before the wrapper runs.","Align module bindings with the host version."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let outcome = std::panic::catch_unwind(std::AssertUnwindSafe(|| invoke_procedure()));\nif outcome.is_err() {\n    // Nested-transaction attempt: log the call stack, restructure so tx-scoped\n    // helpers are not invoked re-entrantly.\n}","preventionTips":["Do not invoke transactional procedures/helpers from inside another open transaction.","Perform HTTP or scheduling side effects after the outer transaction commits.","Remove any manual procedure tx syscalls from module code."],"tags":["panic","transaction","nested-transaction","wasm","rust"],"backgroundTag":"nested-transaction-error","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}