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
- Ensure every row has a stable primary key (`id`) before it can be updated; generate the key client-side on optimistic insert.
- Await the insert (or refresh) so the server-assigned key is present before issuing updates.
- Filter out or reject updates for keyless rows in the UI before opening the transaction.
- 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
- Generate client-side IDs (uuid) on optimistic inserts so keys always exist
- Only enable edit controls on rows with a non-null key
- Await insert completion before allowing updates on new rows
- Map the collection schema's key to the actual primary-key field
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
- Failed to create ${mutation.name} (or server-provided messag
- Fallback response for "${table}" is not an object
- Fallback response missing "${table}" array
- Failed to bulk update ${mutation.name} (or server-provided m
- Failed to list projects (${res.status})
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/421c273c5de174fd.
Report an issue: GitHub.