Budibase/budibase · error · HTTPError

Project revision does not match.

Error message

Project revision does not match.

What it means

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.

Source

Thrown at packages/server/src/sdk/workspace/projects/crud.ts:253

    }
  } catch (err) {
    if (!(err instanceof HTTPError)) {
      await rollbackAssignments(rollbacks)
    }
    throw err
  }

  return rollbacks
}

export async function remove(id: string, rev: string) {
  const db = context.getWorkspaceDB()
  const project = await get(id)
  if (!project) {
    throw new HTTPError(`Project with id '${id}' not found.`, 404)
  }
  if (project._rev !== rev) {
    throw new HTTPError("Project revision does not match.", 409)
  }

  const rollbacks = await clearAssignments(id)
  try {
    return await db.remove(id, rev)
  } catch (err) {
    await rollbackAssignments(rollbacks)
    throw err
  }
}

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Re-fetch the project with get(id) and pass its current _rev to remove()
  2. On 409, reload the document and retry the delete once with the fresh rev
  3. Ensure you read the project through the same workspace context you delete in, so revs come from the same DB
  4. Serialize delete operations (e.g. disable the delete button after first click) to avoid concurrent deletes

Example fix

// before
await projects.remove(id, staleRev)
// after
const project = await projects.get(id)
await projects.remove(id, project._rev)
Defensive patterns

Strategy: retry

Validate before calling

const current = await projects.get(id)
if (current._rev !== rev) rev = current._rev // refresh before deleting

Type guard

function hasRev(p: unknown): p is Project & { _rev: string }
  return !!p && typeof (p as Project)._rev === "string"

Try / catch

try {
  await projects.remove(id, rev)
} catch (e) {
  if (e?.status === 409) {
    const fresh = await projects.get(id)
    if (fresh) return projects.remove(id, fresh._rev)
  }
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/9f45e757776578c5. Report an issue: GitHub.