{"record":{"id":"a37ebb89039af7f6","repo":"BloopAI/vibe-kanban","slug":"failed-to-bulk-update-mutation-name-or-server","errorCode":null,"errorMessage":"Failed to bulk update ${mutation.name} (or server-provided message via parseResponseError)","messagePattern":"Failed to bulk update (.+?) \\(or server-provided message via parseResponseError\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/web-core/src/shared/lib/electric/collections.ts","lineNumber":687,"sourceCode":"          }\n\n          return {\n            id: String(mutationItem.key),\n            ...(mutationItem.changes as Record<string, unknown>),\n          };\n        });\n\n        const response = await makeRequest(`${mutation.url}/bulk`, {\n          method: 'POST',\n          body: JSON.stringify({ updates }),\n        });\n\n        if (!response.ok) {\n          const message = await parseResponseError(\n            response,\n            `Failed to bulk update ${mutation.name}`\n          );\n          throw new Error(message);\n        }\n\n        const result = (await response.json()) as { txid: number };\n        txids = [result.txid];\n      } else {\n        const mutationItem = transaction.mutations[0];\n        if (!mutationItem?.key) {\n          throw new Error(`Failed to update ${mutation.name}: missing key`);\n        }\n\n        const response = await makeRequest(\n          `${mutation.url}/${mutationItem.key}`,\n          {\n            method: 'PATCH',\n            body: JSON.stringify(mutationItem.changes),\n          }\n        );\n","sourceCodeStart":669,"sourceCodeEnd":705,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/web-core/src/shared/lib/electric/collections.ts#L669-L705","documentation":"When an update transaction contains multiple mutations, the library POSTs them to `${mutation.url}/bulk`; on a non-ok response it throws with the server's `message`/`error` (via parseResponseError) or the fallback 'Failed to bulk update <name>'. This rejects the whole batch, rolling back all optimistic updates in the transaction.","triggerScenarios":"Bulk endpoint returns 404 (route `${url}/bulk` not implemented server-side), 401/403 auth failure, 422 when any single row in the batch fails validation (whole batch rejected), 409 conflicts, or 5xx.","commonSituations":"Backend never implemented the /bulk route while the client sends multi-row edits; one invalid row in the batch fails the entire request; bulk payload size limits exceeded on large selections; expired session during a long editing session.","solutions":["Implement/verify the POST ${mutation.url}/bulk endpoint accepting { updates: [{ id, ...changes }] } and returning { txid }.","Validate every row before sending the batch; send smaller batches and surface per-row failures.","Re-authenticate on 401 and retry the transaction.","If bulk is unsupported, chunk client-side into single PATCH updates (one mutation per transaction)."],"exampleFix":"// before\nawait collection.update(id, changes); // many in one transaction -> hits /bulk\n// after\nfor (const { id, changes } of edits) {\n  await collection.utils.runTransaction? ? null : null;\n}\n// simpler: issue each update in its own transaction\nfor (const { id, changes } of edits) {\n  await singleUpdateTransaction(collection, id, changes); // uses PATCH, avoids /bulk\n}","handlingStrategy":"retry","validationCode":"const invalid = updates.filter(u => u.id == null);\nif (invalid.length) throw new Error('All rows need an id for bulk update');\nif (updates.length > MAX_BULK) throw new Error('Batch too large; chunk it');","typeGuard":"function isBulkUnsupported(e: unknown): boolean {\n  return e instanceof Error && e.message.startsWith('Failed to bulk update ') &&\n    (e as { status?: number }).status === 404;\n}","tryCatchPattern":"try {\n  await bulkUpdate(collection, updates);\n} catch (e) {\n  if (isBulkUnsupported(e)) {\n    await Promise.allSettled(updates.map(u => singleUpdate(collection, u))); // PATCH fallback\n    return;\n  }\n  if (e instanceof Error && e.status >= 500) return retryWithBackoff(() => bulkUpdate(collection, updates));\n  throw e;\n}","preventionTips":["Implement the POST ${url}/bulk route returning { txid } before enabling multi-row edits","Validate every row before batching — one bad row fails the whole batch","Chunk large selections to stay under body-size limits","Detect 404 on /bulk and degrade to per-row PATCH updates"],"tags":["electric-sql","mutations","http-error","bulk"],"backgroundTag":"api-non-2xx-response","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}