{"record":{"id":"5bd1b006dd81a9dc","repo":"Budibase/budibase","slug":"this-action-cannot-be-performed-for-production-app","errorCode":null,"errorMessage":"This action cannot be performed for production apps","messagePattern":"This action cannot be performed for production apps","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/server/src/sdk/workspace/workspaces/sync.ts","lineNumber":171,"sourceCode":"\nexport async function syncUsersAcrossWorkspaces(userIds: string[]) {\n  const devWorkspaceIds = await dbCore.getDevWorkspaceIDs()\n  await syncUsersAgainstWorkspaces(userIds, devWorkspaceIds)\n}\n\nexport async function syncWorkspace(\n  workspaceId: string,\n  opts?: { automationOnly?: boolean }\n): Promise<{ message: string }> {\n  if (env.DISABLE_AUTO_PROD_APP_SYNC) {\n    return {\n      message:\n        \"App sync disabled. You can reenable with the DISABLE_AUTO_PROD_APP_SYNC environment variable.\",\n    }\n  }\n\n  if (dbCore.isProdWorkspaceID(workspaceId)) {\n    throw new Error(\"This action cannot be performed for production apps\")\n  }\n\n  // replicate prod to dev\n  const prodWorkspaceId = dbCore.getProdWorkspaceID(workspaceId)\n\n  // specific case, want to make sure setup is skipped\n  const prodDb = context.getProdWorkspaceDB({ skip_setup: true })\n  const exists = await prodDb.exists()\n\n  let error\n  if (exists) {\n    const replication = new dbCore.Replication({\n      source: prodWorkspaceId,\n      target: workspaceId,\n    })\n    try {\n      const replOpts = replication.appReplicateOpts()\n      if (opts?.automationOnly) {","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/workspaces/sync.ts#L153-L189","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Convert the id first: call dbCore.getDevWorkspaceID(prodId) and pass the dev id","Guard the call: only invoke when !dbCore.isProdWorkspaceID(workspaceId)","Fix the caller to source the dev workspace id (e.g. from the app metadata rather than the published URL)"],"exampleFix":"// before\nawait sync.syncWorkspace(appId)\n// after\nconst devId = dbCore.isProdWorkspaceID(appId) ? dbCore.getDevWorkspaceID(appId) : appId\nawait sync.syncWorkspace(devId)","handlingStrategy":"validation","validationCode":"if (dbCore.isProdWorkspaceID(workspaceId)) {\n  workspaceId = dbCore.getDevWorkspaceID(workspaceId)\n}","typeGuard":"function isDevWorkspace(id) {\n  return !dbCore.isProdWorkspaceID(id)\n}","tryCatchPattern":"try {\n  await sync.syncWorkspace(devId)\n} catch (e) {\n  if (e?.message?.includes('cannot be performed for production apps')) {\n    // caller passed a prod id; convert and retry\n  } else throw e\n}","preventionTips":["Always derive the dev id with getDevWorkspaceID before syncing","Never source workspace ids from published app URLs","Guard sync entry points with isProdWorkspaceID checks"],"tags":["production-guard","workspace-sync","dev-prod"],"backgroundTag":"prod-workspace-guard","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}