{"record":{"id":"bbc7d824b45eb15b","repo":"toeverything/AFFiNE","slug":"can-not-batch-grant-doc-owner-permissions","errorCode":"can_not_batch_grant_doc_owner_permissions","errorMessage":"Can not batch grant doc owner permissions.","messagePattern":"Can not batch grant doc owner permissions\\.","errorType":"error_code","errorClass":"CanNotBatchGrantDocOwnerPermissions","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/models/doc-user.ts","lineNumber":67,"sourceCode":"    assert(role !== DocRole.Owner, 'Cannot set Owner role of a doc to a user.');\n\n    await this.models.docGrant.set(workspaceId, docId, userId, role);\n    return await this.get(workspaceId, docId, userId);\n  }\n\n  @Transactional()\n  async batchSetUserRoles(\n    workspaceId: string,\n    docId: string,\n    userIds: string[],\n    role: DocRole\n  ) {\n    if (userIds.length === 0) {\n      return 0;\n    }\n\n    if (role === DocRole.Owner) {\n      throw new CanNotBatchGrantDocOwnerPermissions();\n    }\n\n    return await this.models.docGrant.batchSetUserRoles(\n      workspaceId,\n      docId,\n      userIds,\n      role\n    );\n  }\n\n  @Transactional()\n  async delete(workspaceId: string, docId: string, userId: string) {\n    await this.models.docGrant.delete(workspaceId, docId, userId);\n  }\n\n  @Transactional()\n  async deleteByUserId(userId: string) {\n    await this.db.docGrant.deleteMany({","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/models/doc-user.ts#L49-L85","documentation":"CanNotBatchGrantDocOwnerPermissions (invalid_input / can_not_batch_grant_doc_owner_permissions) thrown by DocUserModel.batchSetUserRoles (packages/backend/server/src/models/doc-user.ts:67). The batch API refuses to assign DocRole.Owner to multiple users at once — owner is a singular, privileged role that must be transferred, not batch-granted. Any call with role === DocRole.Owner throws immediately (after the empty-list short-circuit).","triggerScenarios":"Calling batchSetUserRoles(workspaceId, docId, userIds, DocRole.Owner) with a non-empty userIds list. The equality check `role === DocRole.Owner` fires and the function throws before delegating to models.docGrant.batchSetUserRoles.","commonSituations":"UI 'make owners' bulk action that reuses the batch endpoint; migrating permissions with a bulk script that includes the owner role; mistaken role mapping that maps 'admin' to DocRole.Owner in a batch.","solutions":["Use the dedicated owner-transfer API (singular) to set a doc owner, not batchSetUserRoles.","Filter Owner out of the role list before calling batch: use Admin/Editor/Viewer for batch grants.","If you truly need multiple owners, revisit the model — doc ownership is intentionally singular.","Catch the code can_not_batch_grant_doc_owner_permissions and surface 'Set owners one at a time' in the UI."],"exampleFix":"// before\nawait docUserModel.batchSetUserRoles(ws, doc, userIds, DocRole.Owner);\n// after\nif (role === DocRole.Owner) throw new Error('Use the owner-transfer endpoint for Owner.');\nawait docUserModel.batchSetUserRoles(ws, doc, userIds, DocRole.Admin);","handlingStrategy":"validation","validationCode":"if (role === DocRole.Owner) {\n  throw new Error('Use the owner-transfer endpoint to set a doc Owner, not batchSetUserRoles');\n}\nawait docUserModel.batchSetUserRoles(workspaceId, docId, userIds, role);","typeGuard":"const isBatchableRole = (r: DocRole): boolean => r !== DocRole.Owner;","tryCatchPattern":"try {\n  await docUserModel.batchSetUserRoles(ws, doc, ids, role);\n} catch (e) {\n  if (e instanceof UserFriendlyError && e.code === 'can_not_batch_grant_doc_owner_permissions') {\n    ui.warn('Set doc owners one at a time.');\n    return;\n  }\n  throw e;\n}","preventionTips":["Filter Owner out of any batch role assignment.","Route Owner through the singular owner-transfer API.","Add a unit test asserting batchSetUserRoles rejects Owner."],"tags":["doc","permission","owner","batch"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}