{"record":{"id":"9f45e757776578c5","repo":"Budibase/budibase","slug":"project-revision-does-not-match","errorCode":null,"errorMessage":"Project revision does not match.","messagePattern":"Project revision does not match\\.","errorType":"http","errorClass":"HTTPError","httpStatus":409,"severity":"error","filePath":"packages/server/src/sdk/workspace/projects/crud.ts","lineNumber":253,"sourceCode":"    }\n  } catch (err) {\n    if (!(err instanceof HTTPError)) {\n      await rollbackAssignments(rollbacks)\n    }\n    throw err\n  }\n\n  return rollbacks\n}\n\nexport async function remove(id: string, rev: string) {\n  const db = context.getWorkspaceDB()\n  const project = await get(id)\n  if (!project) {\n    throw new HTTPError(`Project with id '${id}' not found.`, 404)\n  }\n  if (project._rev !== rev) {\n    throw new HTTPError(\"Project revision does not match.\", 409)\n  }\n\n  const rollbacks = await clearAssignments(id)\n  try {\n    return await db.remove(id, rev)\n  } catch (err) {\n    await rollbackAssignments(rollbacks)\n    throw err\n  }\n}\n","sourceCodeStart":235,"sourceCodeEnd":264,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/projects/crud.ts#L235-L264","documentation":"This 409 HTTPError is thrown when the _rev supplied to remove(id, rev) does not match the project's current revision in the workspace DB. It is an optimistic-concurrency guard ensuring the caller saw the latest state of the document before deleting it.","triggerScenarios":"Calling remove(id, rev) with an old or fabricated rev: the project was updated after the caller fetched it, the caller passed a rev from a different document, or a second delete attempt reuses a rev already consumed by the first successful delete.","commonSituations":"Two admins deleting the same app concurrently; a stale client cached the project list before another user edited the app; retrying a delete after the first one partially succeeded; passing `undefined`/wrong property (e.g. the project object instead of its _rev).","solutions":["Re-fetch the project with get(id) and pass its current _rev to remove()","On 409, reload the document and retry the delete once with the fresh rev","Ensure you read the project through the same workspace context you delete in, so revs come from the same DB","Serialize delete operations (e.g. disable the delete button after first click) to avoid concurrent deletes"],"exampleFix":"// before\nawait projects.remove(id, staleRev)\n// after\nconst project = await projects.get(id)\nawait projects.remove(id, project._rev)","handlingStrategy":"retry","validationCode":"const current = await projects.get(id)\nif (current._rev !== rev) rev = current._rev // refresh before deleting","typeGuard":"function hasRev(p: unknown): p is Project & { _rev: string }\n  return !!p && typeof (p as Project)._rev === \"string\"","tryCatchPattern":"try {\n  await projects.remove(id, rev)\n} catch (e) {\n  if (e?.status === 409) {\n    const fresh = await projects.get(id)\n    if (fresh) return projects.remove(id, fresh._rev)\n  }\n  throw e\n}","preventionTips":["Always pass the _rev from the most recent read, never a cached value","Serialize destructive operations to avoid concurrent deletes","Disable the delete action after first click; on 409 reload and retry once","Ensure reads and writes use the same workspace context"],"tags":["http-409","conflict","optimistic-concurrency","couchdb"],"backgroundTag":"document-revision-conflict","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}