{"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":"exception","errorClass":"OwnerCanNotLeaveWorkspace","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/member.ts","lineNumber":721,"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":703,"sourceCodeEnd":739,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/49536827790702ad0ab6d3be3405d891a6b19f83/packages/backend/server/src/core/workspaces/resolvers/member.ts#L703-L739","documentation":"Thrown by leaveWorkspace when the caller's role type is WorkspaceRole.Owner. AFFiNE forbids the last controlling owner from abandoning a workspace via leave, because the workspace would be left ownerless.","triggerScenarios":"The workspace owner account calls leaveWorkspace(workspaceId) for a workspace where its role.type === 'Owner'.","commonSituations":"Owner tries to leave during account cleanup or team handover; automation/test scripts that create a workspace and then try to leave it with the owner identity.","solutions":["Transfer ownership to another member first (workspace settings → transfer ownership), then leave as a regular member.","If you want the workspace gone, delete the workspace instead of leaving it.","If ownership transfer already happened, sign out/in or refresh the session so the updated role is used."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before showing/calling leave, check the user's role in the workspace\nconst { workspace } = await gql.request(GET_WORKSPACE, { id: workspaceId });\nif (workspace.permissions.role === 'Owner') {\n  // offer 'transfer ownership' or 'delete workspace' instead of leave\n  showOwnerLeaveGuard();\n} else {\n  await gql.request(LEAVE_WORKSPACE, { workspaceId });\n}","typeGuard":"const isWorkspaceOwner = (role?: string): boolean => role === 'Owner';","tryCatchPattern":"try {\n  await leaveWorkspace(workspaceId);\n} catch (e) {\n  if (getErrorCode(e) === 'owner_can_not_leave_workspace') {\n    openTransferOwnershipDialog(workspaceId);\n  }\n}","preventionTips":["Hide the leave option for owners and offer ownership transfer / workspace deletion instead.","In automation, never leave with the creator/owner identity; create a second admin member first."],"tags":["workspace","owner","leave","member","graphql"],"backgroundTag":"owner-restriction","analyzedSha":"49536827790702ad0ab6d3be3405d891a6b19f83","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}