Budibase/budibase · error

This action cannot be performed for production apps

Error message

This action cannot be performed for production apps

What it means

syncWorkspace replicates a production workspace into its development counterpart. It explicitly rejects production workspace ids — this action only makes sense from a dev app — throwing a plain Error when dbCore.isProdWorkspaceID(workspaceId) is true.

Source

Thrown at packages/server/src/sdk/workspace/workspaces/sync.ts:171

export async function syncUsersAcrossWorkspaces(userIds: string[]) {
  const devWorkspaceIds = await dbCore.getDevWorkspaceIDs()
  await syncUsersAgainstWorkspaces(userIds, devWorkspaceIds)
}

export async function syncWorkspace(
  workspaceId: string,
  opts?: { automationOnly?: boolean }
): Promise<{ message: string }> {
  if (env.DISABLE_AUTO_PROD_APP_SYNC) {
    return {
      message:
        "App sync disabled. You can reenable with the DISABLE_AUTO_PROD_APP_SYNC environment variable.",
    }
  }

  if (dbCore.isProdWorkspaceID(workspaceId)) {
    throw new Error("This action cannot be performed for production apps")
  }

  // replicate prod to dev
  const prodWorkspaceId = dbCore.getProdWorkspaceID(workspaceId)

  // specific case, want to make sure setup is skipped
  const prodDb = context.getProdWorkspaceDB({ skip_setup: true })
  const exists = await prodDb.exists()

  let error
  if (exists) {
    const replication = new dbCore.Replication({
      source: prodWorkspaceId,
      target: workspaceId,
    })
    try {
      const replOpts = replication.appReplicateOpts()
      if (opts?.automationOnly) {

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Convert the id first: call dbCore.getDevWorkspaceID(prodId) and pass the dev id
  2. Guard the call: only invoke when !dbCore.isProdWorkspaceID(workspaceId)
  3. Fix the caller to source the dev workspace id (e.g. from the app metadata rather than the published URL)

Example fix

// before
await sync.syncWorkspace(appId)
// after
const devId = dbCore.isProdWorkspaceID(appId) ? dbCore.getDevWorkspaceID(appId) : appId
await sync.syncWorkspace(devId)
Defensive patterns

Strategy: validation

Validate before calling

if (dbCore.isProdWorkspaceID(workspaceId)) {
  workspaceId = dbCore.getDevWorkspaceID(workspaceId)
}

Type guard

function isDevWorkspace(id) {
  return !dbCore.isProdWorkspaceID(id)
}

Try / catch

try {
  await sync.syncWorkspace(devId)
} catch (e) {
  if (e?.message?.includes('cannot be performed for production apps')) {
    // caller passed a prod id; convert and retry
  } else throw e
}

Prevention

When it happens

Trigger: Calling syncWorkspace/initDeployedApp/sync/syncDevApp with a production workspace id (one ending in the prod suffix, e.g. workspaceId without '-dev', or explicitly derived via getProdWorkspaceID).

Common situations: Automation or scripts that resolve the app id from a published app URL (which uses the prod id); calling sync on the wrong side of dev/prod; wiring a deploy hook to sync using the prod id by mistake.


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