clockworklabs/SpacetimeDB · error

Attempt to remove non-existent insert callback

Error message

Attempt to remove non-existent insert callback

What it means

The Rust client SDK registers on_insert callbacks by CallbackId in an internal map; `remove_on_insert` expects the id to be present and panics when `remove` returns None. These methods are pub(crate), so a user hitting this is experiencing SDK-internal double-removal or removal of an unregistered id — an SDK bug or version regression, not user callback misuse.

Source

Thrown at sdks/rust/src/callbacks.rs:157

    pub(crate) fn register_on_delete(&mut self, callback_id: CallbackId, callback: RowCallback<M>) {
        self.on_delete.insert(callback_id, callback);
    }

    pub(crate) fn register_on_update(&mut self, callback_id: CallbackId, callback: UpdateCallback<M>) {
        self.on_update.insert(callback_id, callback);
    }

    pub(crate) fn remove_on_insert(&mut self, callback_id: CallbackId) {
        // Ugly: `impl FnMut` is `must_use`.
        // If we don't `.expect` this, no diagnostic,
        // but we want to assert that we actually removed a callback,
        // we just don't want to invoke it.
        // So we have to `let _ =`.
        let _ = self
            .on_insert
            .remove(&callback_id)
            .expect("Attempt to remove non-existent insert callback");
    }

    pub(crate) fn remove_on_delete(&mut self, callback_id: CallbackId) {
        // Ugly: `impl FnMut` is `must_use`.
        // If we don't `.expect` this, no diagnostic,
        // but we want to assert that we actually removed a callback,
        // we just don't want to invoke it.
        // So we have to `let _ =`.
        let _ = self
            .on_delete
            .remove(&callback_id)
            .expect("Attempt to remove non-existent delete callback");
    }

    pub(crate) fn remove_on_update(&mut self, callback_id: CallbackId) {
        // Ugly: `impl FnMut` is `must_use`.
        // If we don't `.expect` this, no diagnostic,
        // but we want to assert that we actually removed a callback,

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Upgrade spacetimedb Rust SDK to the latest patch release; search the repo's issues for 'Attempt to remove non-existent insert callback'.
  2. Minimize unusual lifecycle churn (repeated register/unregister or re-init of the same client) until on a fixed version.
  3. If patching locally, downgrade the expect to a warning no-op remove and report the repro upstream.

Example fix

// before (SDK internal)
let _ = self.on_insert.remove(&callback_id).expect("Attempt to remove non-existent insert callback");

// after (defensive)
if self.on_insert.remove(&callback_id).is_none() {
    tracing::warn!("on_insert callback {:?} already removed", callback_id);
}
Defensive patterns

Strategy: validation

Validate before calling

// SDK users: if you maintain callback ids, never tear down the same handle twice;
// conceptually: if callbacks.contains_key(&id) { remove_on_insert(id); }

Try / catch

let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| client.disconnect())); if r.is_err() { /* SDK bug: capture panic, recreate the client */ }

Prevention

When it happens

Trigger: SDK-internal lifecycle paths (client recreation, subscription teardown, reducer lifecycle) calling remove_on_insert twice for the same CallbackId, or removing after a registration path failed — typically under async races during disconnect/reconnect.

Common situations: Upgrading the spacetimedb Rust SDK and hitting a lifecycle regression; rapidly disconnecting/reconnecting a client; teardown racing a subscription update callback.

Related errors


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