{"record":{"id":"22512e78e4c072a7","repo":"clockworklabs/SpacetimeDB","slug":"attempt-to-remove-non-existent-delete-callback","errorCode":null,"errorMessage":"Attempt to remove non-existent delete callback","messagePattern":"Attempt to remove non-existent delete callback","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sdks/rust/src/callbacks.rs","lineNumber":169,"sourceCode":"        // but we want to assert that we actually removed a callback,\n        // we just don't want to invoke it.\n        // So we have to `let _ =`.\n        let _ = self\n            .on_insert\n            .remove(&callback_id)\n            .expect(\"Attempt to remove non-existent insert callback\");\n    }\n\n    pub(crate) fn remove_on_delete(&mut self, callback_id: CallbackId) {\n        // Ugly: `impl FnMut` is `must_use`.\n        // If we don't `.expect` this, no diagnostic,\n        // but we want to assert that we actually removed a callback,\n        // we just don't want to invoke it.\n        // So we have to `let _ =`.\n        let _ = self\n            .on_delete\n            .remove(&callback_id)\n            .expect(\"Attempt to remove non-existent delete callback\");\n    }\n\n    pub(crate) fn remove_on_update(&mut self, callback_id: CallbackId) {\n        // Ugly: `impl FnMut` is `must_use`.\n        // If we don't `.expect` this, no diagnostic,\n        // but we want to assert that we actually removed a callback,\n        // we just don't want to invoke it.\n        // So we have to `let _ =`.\n        let _ = self\n            .on_update\n            .remove(&callback_id)\n            .expect(\"Attempt to remove non-existent update callback\");\n    }\n\n    fn invoke_on_insert(&mut self, ctx: &M::EventContext, row: &dyn Any) {\n        for callback in self.on_insert.values_mut() {\n            callback(ctx, row);\n        }","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/sdks/rust/src/callbacks.rs#L151-L187","documentation":"The Rust client SDK tracks on_delete callbacks by CallbackId in an internal map; `remove_on_delete` expects the id to be present and panics when `remove` returns None. The method is pub(crate), so users see this only through SDK internals double-removing or removing an unregistered id — an SDK bug or version regression rather than user callback misuse.","triggerScenarios":"SDK-internal teardown paths (disconnect, subscription replacement, client drop races) invoking remove_on_delete twice for the same CallbackId or after a failed registration.","commonSituations":"SDK upgrade regressions; rapid unsubscribe/reconnect cycles; teardown racing an in-flight subscription update that touches on_delete handlers.","solutions":["Upgrade the spacetimedb Rust SDK; check the issue tracker for 'Attempt to remove non-existent delete callback'.","Avoid aggressive repeated unsubscribe/reconnect churn on one client while on a known-bad version.","Patch locally by making the remove tolerant (log instead of panic) and report the repro."],"exampleFix":"// before (SDK internal)\nlet _ = self.on_delete.remove(&callback_id).expect(\"Attempt to remove non-existent delete callback\");\n\n// after (defensive)\nif self.on_delete.remove(&callback_id).is_none() {\n    tracing::warn!(\"on_delete callback {:?} already removed\", callback_id);\n}","handlingStrategy":"validation","validationCode":"// SDK users: guard against double teardown of the same callback handle before unsubscribing","typeGuard":null,"tryCatchPattern":"let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| client.disconnect())); if r.is_err() { /* SDK bug: log panic, rebuild client state */ }","preventionTips":["Avoid rapid unsubscribe/reconnect churn on one client instance.","Update the SDK promptly; these pub(crate) expects are internal invariants.","File an issue with the panic backtrace and lifecycle sequence when hit."],"tags":["rust","spacetimedb-sdk","callbacks","client","double-remove"],"backgroundTag":"callback-already-removed","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}