{"record":{"id":"b72bbf41ab24fd6e","repo":"Budibase/budibase","slug":"unable-to-bulk-remove-documents-res-error","errorCode":null,"errorMessage":"Unable to bulk remove documents: ${res.error}","messagePattern":"Unable to bulk remove documents: (.+?)","errorType":"exception","errorClass":"CouchDBError","httpStatus":400,"severity":"error","filePath":"packages/backend-core/src/db/couch/DatabaseImpl.ts","lineNumber":310,"sourceCode":"          docs: documents.map(doc => ({\n            ...doc,\n            _deleted: true,\n          })),\n        })\n    })\n    if (opts?.silenceErrors) {\n      return\n    }\n    let errorFound = false\n    let errorMessage = \"Unable to bulk remove documents: \"\n    for (let res of response) {\n      if (res.error) {\n        errorFound = true\n        errorMessage += res.error\n      }\n    }\n    if (errorFound) {\n      throw new CouchDBError(errorMessage, {\n        name: this.name,\n        status: 400,\n      })\n    }\n  }\n\n  async post(document: AnyDocument, opts?: DatabasePutOpts) {\n    if (!document._id) {\n      document._id = newid()\n    }\n    return this.put(document, opts)\n  }\n\n  async put(document: AnyDocument, opts?: DatabasePutOpts) {\n    if (!document._id) {\n      throw new Error(\"Cannot store document without _id field.\")\n    }\n    return this.performCallWithDBCreation(async db => {","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/backend-core/src/db/couch/DatabaseImpl.ts#L292-L328","documentation":"bulkRemove in DatabaseImpl aggregates per-document errors from a CouchDB bulk docs response and, if any document failed to delete, throws a CouchDBError with status 400 containing every accumulated error message. It means at least one document in the batch could not be removed, while others may have been deleted successfully (partial failure is possible).","triggerScenarios":"Calling db.bulkRemove(docs) where one or more docs have a bad/missing _id, a wrong _rev (conflict), or the doc does not exist; CouchDB returns an error object for those rows.","commonSituations":"Deleting stale UI state that was concurrently modified or deleted elsewhere; passing docs fetched long ago with outdated _rev; bulk-deleting ids that never existed; permission/restricted design docs in the batch.","solutions":["Re-fetch each document immediately before bulkRemove so _id and _rev are current","Parse res.error rows from the CouchDB bulk response and retry only the failed docs individually with force/updated revs","Check that no protected/design documents (starting with _design/) are included in the batch","Verify the user/role has write access to the database"],"exampleFix":"// before\nawait db.bulkRemove(staleDocs)\n// after\nconst fresh = await db.bulkGet({ docs: staleDocs.map(d => ({ id: d._id })) })\nconst deletable = fresh.results.map(r => r.docs[0].ok).filter(Boolean).map(d => ({ ...d, _deleted: true }))\nawait db.bulkRemove(deletable)","handlingStrategy":"try-catch","validationCode":"const invalid = docs.filter(d => !d._id || !d._rev)\nif (invalid.length) throw new Error(`Docs missing _id/_rev: ${invalid.map(d => d._id).join(\",\")}`)","typeGuard":"function isDeletable(d: any): d is { _id: string; _rev: string } {\n  return typeof d._id === \"string\" && typeof d._rev === \"string\"\n}","tryCatchPattern":"try {\n  await db.bulkRemove(docs)\n} catch (e) {\n  if (e.name === \"CouchDBError\" && e.status === 400) {\n    // re-fetch failed docs individually and delete with fresh revs\n  }\n}","preventionTips":["Always bulk-remove freshly fetched documents","Filter out _design/ docs before deleting","Handle partial failure: re-read docs and retry only failures"],"tags":["couchdb","bulk-operations","conflict"],"backgroundTag":"couchdb-document-conflict","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}