{"record":{"id":"bde269f0ada9f6cc","repo":"toeverything/AFFiNE","slug":"owner-can-not-leave-workspace","errorCode":"owner_can_not_leave_workspace","errorMessage":"Owner can not leave the workspace.","messagePattern":"Owner can not leave the workspace\\.","errorType":"http","errorClass":"OwnerCanNotLeaveWorkspace","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/member.ts","lineNumber":770,"sourceCode":"      nullable: true,\n      deprecationReason: 'no used anymore',\n    })\n    _sendLeaveMail?: boolean,\n    @Args('workspaceName', {\n      nullable: true,\n      deprecationReason: 'no longer used',\n    })\n    _workspaceName?: string\n  ) {\n    const role = await this.models.workspaceUser.getActive(\n      workspaceId,\n      user.id\n    );\n    if (!role) {\n      throw new MemberNotFoundInSpace({ spaceId: workspaceId });\n    }\n    if (role.type === WorkspaceRole.Owner) {\n      throw new OwnerCanNotLeaveWorkspace();\n    }\n\n    await this.models.workspaceUser.delete(workspaceId, user.id);\n    this.event.emit('workspace.members.leave', {\n      workspaceId,\n      userId: user.id,\n    });\n\n    this.event.emit('workspace.members.updated', {\n      workspaceId,\n    });\n    await this.policy.reconcileWorkspaceQuotaState(workspaceId);\n\n    return true;\n  }\n\n  private async acceptInvitationByEmail(role: WorkspaceUserCompat) {\n    await this.assertWorkspaceAcceptsMemberChange(role.workspaceId);","sourceCodeStart":752,"sourceCodeEnd":788,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/workspaces/resolvers/member.ts#L752-L788","documentation":"Thrown by `leaveWorkspace` when the caller is the workspace `Owner`. Workspaces must always retain an owner, so the owner cannot self-remove; ownership must be transferred first. Coded `owner_can_not_leave_workspace` (action_forbidden).","triggerScenarios":"The user whose `role.type === WorkspaceRole.Owner` calls `leaveWorkspace` on their own workspace. Common when the sole/last admin tries to leave without designating a successor.","commonSituations":"Founder/owner leaving the org without transferring ownership; UI does not distinguish owner from regular members in the leave action; script that bulk-leaves workspaces hits one the operator owns.","solutions":["Transfer ownership to another admin member first via the workspace settings UI, then leave.","Disable the Leave control when `role === 'Owner'` and surface a 'transfer ownership first' message.","If leaving permanently, assign a new owner and only then call `leaveWorkspace`.","For workspaces with no other members, add a new owner before leaving or delete the workspace instead."],"exampleFix":"// before\nawait sdk.leaveWorkspace({ workspaceId });\n\n// after\nif (myRole === 'Owner') {\n  notify('Transfer ownership to another member before leaving.');\n  openTransferOwnershipDialog();\n  return;\n}\nawait sdk.leaveWorkspace({ workspaceId });","handlingStrategy":"validation","validationCode":"// Block leave for owners\nif (myRole === 'Owner') {\n  openTransferOwnershipDialog();\n  return;\n}\nawait sdk.leaveWorkspace({ workspaceId });","typeGuard":"function isOwner(role) {\n  return role === 'Owner';\n}","tryCatchPattern":"try {\n  await sdk.leaveWorkspace({ workspaceId });\n} catch (e) {\n  if (e.code === 'owner_can_not_leave_workspace') {\n    notify('Transfer ownership to another member before leaving.');\n    openTransferOwnershipDialog();\n  } else throw e;\n}","preventionTips":["Disable the Leave control when the user's role is Owner.","Always transfer ownership before attempting to leave.","Surface the transfer-ownership flow proactively for sole owners."],"tags":["graphql","workspace","members","owner","business-rule","nestjs"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}