BloopAI/vibe-kanban · error

Failed to update ${mutation.name}: missing key

Error message

Failed to update ${mutation.name}: missing key

What it means

In the onUpdate mutation handler, when a transaction contains multiple mutations the library builds a bulk-update payload; each mutation item must carry a row key, and this error is thrown for any item whose `key` is undefined/null. Without a key the library cannot construct the { id, ...changes } entry for the bulk endpoint.

Source

Thrown at packages/web-core/src/shared/lib/electric/collections.ts:668

      maybeRefreshFallbackAfterMutation(sourceKey);

      if (isSourceFallbackLocked(sourceKey)) {
        return;
      }

      return { txid: txids };
    },

    onUpdate: async ({
      transaction,
    }: MutationFnParams): Promise<{ txid: number[] } | void> => {
      let txids: number[] = [];

      if (transaction.mutations.length > 1) {
        const updates = transaction.mutations.map((mutationItem) => {
          if (!mutationItem.key) {
            throw new Error(`Failed to update ${mutation.name}: missing key`);
          }

          return {
            id: String(mutationItem.key),
            ...(mutationItem.changes as Record<string, unknown>),
          };
        });

        const response = await makeRequest(`${mutation.url}/bulk`, {
          method: 'POST',
          body: JSON.stringify({ updates }),
        });

        if (!response.ok) {
          const message = await parseResponseError(
            response,
            `Failed to bulk update ${mutation.name}`
          );

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Ensure every row has a stable primary key (`id`) before it can be updated; generate the key client-side on optimistic insert.
  2. Await the insert (or refresh) so the server-assigned key is present before issuing updates.
  3. Filter out or reject updates for keyless rows in the UI before opening the transaction.
  4. Verify the collection's schema maps the correct field as the Electric primary key.

Example fix

// before
await Promise.all(rows.map(r => collection.update({ id: r.uid, ...changes })));
// after
const updatable = rows.filter(r => r.id != null);
if (updatable.length !== rows.length) warnUser('Some rows have no ID and were skipped');
await Promise.all(updatable.map(r => collection.update({ id: r.id, ...changes })));
Defensive patterns

Strategy: validation

Validate before calling

const missing = rows.filter(r => r.id == null);
if (missing.length > 0) {
  throw new Error(`${missing.length} row(s) have no primary key and cannot be updated`);
}

Type guard

function hasKey(m: { key?: string | number }): m is { key: string | number } {
  return m.key != null;
}

Try / catch

try {
  await runBatchUpdate(collection, rows);
} catch (e) {
  if (e instanceof Error && e.message.includes('missing key')) {
    showToast('Some selected rows were never saved and cannot be edited');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: An update transaction where mutationItem.key is missing — typically updating a row that was inserted optimistically and not yet persisted/assigned an ID, or rows whose primary key field isn't mapped as the Electric key (e.g. id is undefined in the row object).

Common situations: Batch-editing rows that were just created offline and have client-generated/undefined keys; selecting rows whose key field is named differently than the collection's id; a bug where insert txid wasn't awaited before the subsequent update in the same transaction.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/421c273c5de174fd. Report an issue: GitHub.