clockworklabs/SpacetimeDB · error · Exception

Invalid row type for table {RemoteTableName}: {oldValue.GetT

Error message

Invalid row type for table {RemoteTableName}: {oldValue.GetType().Name}

What it means

In the wasUpdated loop of Table.Apply, the OLD value for each updated key must be a Row so the internal delete handler can fire for it. This variant means the previously cached value (not the incoming one) is not a Row instance, i.e. the cache was populated with non-Row values at some earlier point. It indicates the same class of invariant break as the insert variant, but surfaces on the update path.

Source

Thrown at sdks/csharp/src/Table.cs:531

            {
                if (value is Row newRow)
                {
                    OnInternalInsertHandler.Invoke(newRow);
                }
                else
                {
                    throw new Exception($"Invalid row type for table {RemoteTableName}: {value.GetType().Name}");
                }
            }
            foreach (var (_, oldValue, newValue) in wasUpdated)
            {
                if (oldValue is Row oldRow)
                {
                    OnInternalDeleteHandler.Invoke(oldRow);
                }
                else
                {
                    throw new Exception($"Invalid row type for table {RemoteTableName}: {oldValue.GetType().Name}");
                }


                if (newValue is Row newRow)
                {
                    OnInternalInsertHandler.Invoke(newRow);
                }
                else
                {
                    throw new Exception($"Invalid row type for table {RemoteTableName}: {newValue.GetType().Name}");
                }
            }
        }

        /// <summary>
        /// Invoked after applying the parsed table update (delta) to this table.
        /// This is when user callbacks (such as OnInsert, OnUpdate, and OnDelete) are actually triggered for the affected rows.
        /// All <see cref="IRemoteTableHandle.Apply"/> operations should be complete before calling PostApply,

View on GitHub (pinned to 9e0d92412f)

Solutions

  1. Regenerate all bindings with the CLI version matching the SDK package so every table's TValue is Row-derived.
  2. Audit custom table/IRemoteTableHandle implementations and derive row types from Row.
  3. If fully generated, report with the table name and delta - old cache values of non-Row type should be impossible.
Defensive patterns

Strategy: type-guard

Type guard

static bool IsRowType(Type t) => typeof(SpacetimeDB.Runtime.Row).IsAssignableFrom(t); // assert for all cached row types

Try / catch

conn.SubscriptionBuilder().OnError(ex => Log.Error($"cached row invariant broken: {ex.Message}"));

Prevention

When it happens

Trigger: A row update arrives for a table whose cache was earlier populated with non-Row values: custom table handle with a plain class as TValue, or mixed-version generated code where insert-variant checks were skipped (older SDK) and an update now hits the check.

Common situations: Partial upgrade: client assembly updated but some generated table files regenerated with an older toolchain; custom table implementations in test harnesses or fixtures.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@9e0d92412f (2026-08-20). Data as JSON: /api/errors/24d75550cf5587fc. Report an issue: GitHub.