clockworklabs/SpacetimeDB · error

unexpected insertion error: {e}

Error message

unexpected insertion error: {e}

What it means

Raised by the bindings' insert path when `datastore_insert_bsatn` returns an errno other than the two the bindings know how to map (`UNIQUE_ALREADY_EXISTS` and `AUTO_INC_OVERFLOW`). Mapped errnos become proper `TryInsertError::UniqueConstraintViolation` / `TryInsertError::AutoIncOverflow` values returned from `try_insert`; any other errno panics with this message. So seeing it means the host rejected the insert for an unexpected reason — most often module/database schema drift or version skew.

Source

Thrown at crates/bindings/src/table.rs:1371

    buf.clear();
    buf.serialize_into(&row).unwrap();

    // Insert row into table.
    // When table has an auto-incrementing column, we must re-decode the changed `buf`.
    let res = sys::datastore_insert_bsatn(table_id, &mut buf).map(|gen_cols| {
        // Let the caller handle any generated columns written back by `sys::datastore_insert_bsatn` to `buf`.
        T::integrate_generated_columns(&mut row, gen_cols);
        row
    });
    res.map_err(|e| {
        let err = match e {
            sys::Errno::UNIQUE_ALREADY_EXISTS => {
                T::UniqueConstraintViolation::get().map(TryInsertError::UniqueConstraintViolation)
            }
            sys::Errno::AUTO_INC_OVERFLOW => T::AutoIncOverflow::get().map(TryInsertError::AutoIncOverflow),
            _ => None,
        };
        err.unwrap_or_else(|| panic!("unexpected insertion error: {e}"))
    })
}

/// Update a row of type `T` to `row` using the index identified by `index_id`.
#[track_caller]
fn update<T: Table>(index_id: IndexId, mut row: T::Row, mut buf: IterBuf) -> T::Row {
    let table_id = T::table_id();
    // Encode the row as bsatn into the buffer `buf`.
    buf.clear();
    buf.serialize_into(&row).unwrap();

    // Insert row into table.
    // When table has an auto-incrementing column, we must re-decode the changed `buf`.
    let res = sys::datastore_update_bsatn(table_id, index_id, &mut buf).map(|gen_cols| {
        // Let the caller handle any generated columns written back by `sys::datastore_update_bsatn` to `buf`.
        T::integrate_generated_columns(&mut row, gen_cols);
        row
    });

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Republish the module so the Rust row type and the database schema agree: `spacetime publish <db>`.
  2. Match the `spacetimedb-bindings` version in Cargo.toml to your server/CLI version.
  3. Switch `insert` to `try_insert` and log/handle `UniqueConstraintViolation` and `AutoIncOverflow` — it makes the expected failure modes explicit, and if this panic still fires the residual errno is diagnostic.
  4. Capture the `{e}` errno in the panic output and report it upstream if versions and schema are confirmed aligned.

Example fix

// before
ctx.db.user().insert(User { id: 0, email: new_email.clone(), .. })?;

// after - expected constraint errors become values, not panics
match ctx.db.user().try_insert(User { id: 0, email: new_email.clone(), .. }) {
    Ok(_) => {}
    Err(TryInsertError::UniqueConstraintViolation(_)) => { /* handle duplicate */ }
    Err(TryInsertError::AutoIncOverflow(_)) => { /* handle overflow */ }
}
Defensive patterns

Strategy: validation

Validate before calling

// Before inserting, check the columns backed by unique indexes:
let exists = ctx.db.user().email().filter(&row.email).next().is_some();
if exists {
    // decide: reject, upsert, or skip — instead of hitting the constraint path
    return Err("email already registered".into());
}
ctx.db.user().insert(row)?;

Try / catch

// Handle the mapped constraint errors; treat anything else as fatal.
match ctx.db.user().try_insert(row) {
    Ok(inserted) => { /* inserted row with generated cols */ }
    Err(TryInsertError::UniqueConstraintViolation(v)) => { /* duplicate */ }
    Err(TryInsertError::AutoIncOverflow(v)) => { /* overflow */ }
}

Prevention

When it happens

Trigger: Calling `ctx.db.<table>().insert(row)` or `try_insert(row)` where the encoded row violates something other than a unique/auto-inc constraint: row layout not matching the table's current column set after an unpublish/republish, bindings crate older/newer than the host's ABI, or a host-internal storage error.

Common situations: Adding a column to the Rust table type and forgetting to republish before inserting; deploying a module built with an outdated `spacetimedb-bindings` against an upgraded server; inserting very large rows that trip a host limit.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/82123099497d2e00. Report an issue: GitHub.