{"record":{"id":"17e78a9c13a405db","repo":"toeverything/AFFiNE","slug":"cannot-delete-account-with-owned-team-workspace","errorCode":"cannot_delete_account_with_owned_team_workspace","errorMessage":"Cannot delete account. You are the owner of one or more team workspaces. Please transfer ownership or delete them first.","messagePattern":"Cannot delete account\\. You are the owner of one or more team workspaces\\. Please transfer ownership or delete them first\\.","errorType":"exception","errorClass":"CannotDeleteAccountWithOwnedTeamWorkspace","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/models/user.ts","lineNumber":192,"sourceCode":"    return user;\n  }\n\n  async ownedWorkspaces(id: string) {\n    return await this.models.workspaceUser.getUserActiveRoles(id, {\n      role: WorkspaceRole.Owner,\n    });\n  }\n\n  async delete(id: string) {\n    const ownedWorkspaces = await this.ownedWorkspaces(id);\n\n    for (const ws of ownedWorkspaces) {\n      const isTeamWorkspace = await this.models.workspace.isTeamWorkspace(\n        ws.workspaceId\n      );\n\n      if (isTeamWorkspace) {\n        throw new CannotDeleteAccountWithOwnedTeamWorkspace();\n      }\n    }\n\n    await this.event.emitAsync('user.preDelete', { id });\n\n    await this.db.workspaceInvitation.deleteMany({\n      where: { inviteeUserId: id },\n    });\n    const user = await this.db.user.delete({ where: { id } });\n    this.event.emit('user.deleted', user);\n\n    return user;\n  }\n\n  async recreateForBan(id: string) {\n    // ban an user barely share the same logic with delete an user,\n    // but keep the record with `disabled` flag\n    // we delete the account and create it again to trigger all cleanups","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/models/user.ts#L174-L210","documentation":"UserModel.delete() refuses to delete an account that still owns at least one team workspace. It iterates ownedWorkspaces(id) and throws CannotDeleteAccountWithOwnedTeamWorkspace as soon as one is a team workspace — before user.preDelete is emitted or any invitation rows are removed, so nothing is partially deleted.","triggerScenarios":"Calling the account deletion API for a user who owns one or more workspaces where workspaceModel.isTeamWorkspace(workspaceId) returns true.","commonSituations":"Self-service 'delete my account' while still owning a shared team workspace; admin bulk cleanup of users who created team workspaces; ownership left on an auto-created workspace after a plan upgrade to team.","solutions":["Transfer ownership of each owned team workspace to another active member (the transferOwner/setOwner flow), then retry deletion.","Or delete the team workspaces entirely if they are no longer needed.","List the user's owned workspaces first so you know exactly which ones block deletion."],"exampleFix":"// before\nawait userModel.delete(userId); // throws if a team workspace is owned\n\n// after\nfor (const ws of await userModel.ownedWorkspaces(userId)) {\n  if (await models.workspace.isTeamWorkspace(ws.workspaceId)) {\n    await workspaceUser.transferOwner(ws.workspaceId, newOwnerId, 'admin');\n  }\n}\nawait userModel.delete(userId);","handlingStrategy":"validation","validationCode":"const owned = await userModel.ownedWorkspaces(userId);\nconst blocking = [];\nfor (const ws of owned) {\n  if (await models.workspace.isTeamWorkspace(ws.workspaceId)) blocking.push(ws);\n}\nif (blocking.length) {\n  // prompt user to transfer ownership or delete these workspaces first\n}","typeGuard":null,"tryCatchPattern":"try {\n  await userModel.delete(userId);\n} catch (e) {\n  if (e instanceof CannotDeleteAccountWithOwnedTeamWorkspace) {\n    // list owned team workspaces and guide the transfer flow\n  } else throw e;\n}","preventionTips":["Surface owned-team-workspace warnings on the delete-account screen before the user confirms.","Keep an admin runbook for transferring workspace ownership.","Never assume delete() is partial — it aborts atomically, so fix ownership and retry."],"tags":["account-deletion","workspace","ownership","team"],"backgroundTag":"ownership-transfer-required","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}