{"record":{"id":"6a7a26462e3611d9","repo":"hcengineering/platform","slug":"delete-allowed-only-for-active-workspaces","errorCode":null,"errorMessage":"Delete allowed only for active workspaces","messagePattern":"Delete allowed only for active workspaces","errorType":"exception","errorClass":"PlatformError","httpStatus":null,"severity":"error","filePath":"server/account/src/serviceOperations.ts","lineNumber":159,"sourceCode":"\n  const workspaceUuids = Array.isArray(workspaceId) ? workspaceId : [workspaceId]\n\n  const workspaces = await getWorkspacesInfoWithStatusByIds(db, workspaceUuids)\n  if (workspaces.length === 0) {\n    throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, {}))\n  }\n\n  let ops = 0\n  for (const workspace of workspaces) {\n    const update: Partial<WorkspaceStatus> = {}\n    switch (event) {\n      case 'reset-attempts':\n        update.processingAttempts = 0\n        update.lastProcessingTime = Date.now() - processingTimeoutMs // To not wait for next step\n        break\n      case 'delete':\n        if (workspace.status.mode !== 'active') {\n          throw new PlatformError(unknownError('Delete allowed only for active workspaces'))\n        }\n\n        update.mode = 'pending-deletion'\n        update.processingAttempts = 0\n        update.processingProgress = 0\n        update.lastProcessingTime = Date.now() - processingTimeoutMs // To not wait for next step\n        break\n      case 'archive':\n        if (!isActiveMode(workspace.status.mode)) {\n          throw new PlatformError(unknownError('Archiving allowed only for active workspaces'))\n        }\n\n        update.mode = 'archiving-pending-backup'\n        update.processingAttempts = 0\n        update.processingProgress = 0\n        update.lastProcessingTime = Date.now() - processingTimeoutMs // To not wait for next step\n        break\n      case 'unarchive':","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/server/account/src/serviceOperations.ts#L141-L177","documentation":"The 'delete' branch of performWorkspaceOperation refuses to delete workspaces that are not currently in 'active' mode. The two-step deletion design marks active workspaces as 'pending-deletion' and a background worker finishes the actual removal; deleting a workspace already in another lifecycle mode (e.g. archiving, archived, pending-deletion) is rejected with unknownError('Delete allowed only for active workspaces').","triggerScenarios":"Calling performWorkspaceOperation with event='delete' (with admin or matching-workspace permission) on a workspace whose status.mode is anything other than 'active' — e.g. the workspace is archived, already pending-deletion, mid-archive/restore, or suspended.","commonSituations":"Double-clicking a delete button so the second request hits a workspace already in 'pending-deletion'; running a cleanup script over all workspaces including archived ones; deleting a workspace that was just archived by another admin; a workspace stuck in an intermediate mode (archiving-pending-backup) after a failed backup job.","solutions":["Check workspace.status.mode before deleting; only issue event='delete' when mode === 'active'.","If the workspace is already 'pending-deletion', do nothing — the background processor will complete the deletion.","For archived workspaces, first run event='unarchive' to return it to active, then issue the delete.","If the workspace is stuck in a transitional mode (e.g. archiving-pending-backup) due to a failed job, investigate/repair the processing state or reset processingAttempts via 'reset-attempts' before retrying."],"exampleFix":"// before — delete regardless of mode\nawait client.performWorkspaceOperation(ctx, adminToken, { workspaceId: wsId, event: 'delete', params: {} })\n// after — guard on mode\nconst [ws] = await getWorkspaceInfo(ctx, adminToken, wsId)\nif (ws.status.mode !== 'active') {\n  if (ws.status.mode === 'pending-deletion') return // already deleting\n  await client.performWorkspaceOperation(ctx, adminToken, { workspaceId: wsId, event: 'unarchive', params: {} })\n}\nawait client.performWorkspaceOperation(ctx, adminToken, { workspaceId: wsId, event: 'delete', params: {} })","handlingStrategy":"validation","validationCode":"const [ws] = await getWorkspacesInfoWithStatusByIds(db, [workspaceId])\nif (ws?.status.mode !== 'active') {\n  throw new Error(`Cannot delete workspace in mode '${ws?.status.mode}'; only active workspaces can be deleted`)\n}","typeGuard":"function isDeletable(ws: { status: { mode: string } } | undefined): ws is { status: { mode: 'active' } } {\n  return ws !== undefined && ws.status.mode === 'active'\n}","tryCatchPattern":"try {\n  await client.performWorkspaceOperation(ctx, token, { workspaceId, event: 'delete', params: {} })\n} catch (err) {\n  if (String(err?.message).includes('Delete allowed only for active workspaces')) {\n    // re-fetch status; skip if pending-deletion, unarchive first if archived\n    return\n  }\n  throw err\n}","preventionTips":["Always fetch fresh workspace status immediately before lifecycle operations.","Treat delete as idempotent: ignore workspaces already in 'pending-deletion'.","Disable delete buttons in UI unless the displayed mode is 'active'.","Serialize lifecycle operations per workspace (locking/queue) to avoid racing transitions."],"tags":["state-machine","workspace-lifecycle","precondition-failed"],"backgroundTag":"invalid-resource-state","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}