toeverything/AFFiNE · error · CanNotBatchGrantDocOwnerPermissions
can_not_batch_grant_doc_owner_permissions
can_not_batch_grant_doc_owner_permissions
Error message
Can not batch grant doc owner permissions.
What it means
CanNotBatchGrantDocOwnerPermissions, thrown by PermissionWriteModel.batchSetUserRoles (permission-write.ts:577-588) - the permission write path enforces the same rule as the doc-user model: DocRole.Owner cannot be batch granted. Here the role guard runs before the empty-list early return, so even userIds = [] with role Owner throws. Other roles are converted with docRoleToNew(role) and upserted per user into docGrant.
Solutions
- Route ownership changes through the owner-transfer flow instead of batch grants
- Grant Admin/Write/Read in batch - only Owner is refused
- Validate the role at the API boundary so callers never reach this throw
Example fix
// before
await permissions.batchSetUserRoles(workspaceId, docId, userIds, role);
// after
if (role === DocRole.Owner) {
throw new Error('Owner cannot be batch granted; use the owner-transfer flow');
}
await permissions.batchSetUserRoles(workspaceId, docId, userIds, role); Defensive patterns
Strategy: validation
Validate before calling
if (role === DocRole.Owner) {
throw new Error('Owner cannot be batch granted; use the owner-transfer flow');
}
await permissions.batchSetUserRoles(workspaceId, docId, userIds, role); Type guard
const isBatchGrantableRole = (r: DocRole): boolean => r !== DocRole.Owner;
Try / catch
try {
await permissions.batchSetUserRoles(workspaceId, docId, userIds, role);
} catch (e) {
if (e instanceof CanNotBatchGrantDocOwnerPermissions) {
// route to the owner-transfer flow instead
}
throw e;
} Prevention
- Validate the role before calling - even empty batches throw for Owner in this model
- Map ownership changes to the dedicated transfer endpoint
- Keep role whitelists in one shared module used by every permission API
When it happens
Trigger: batchSetUserRoles(workspaceId, docId, userIds, DocRole.Owner) with any userIds value, including an empty array, since the Owner check precedes the length check in this model.
Common situations: Role dropdowns that include Owner; migrating permission data by mapping an 'owner' flag straight into a batch grant call.
Related errors
- can_not_batch_grant_doc_owner_permissions
- -32001
- doc_action_denied
- doc_default_role_can_not_be_owner
- expect_to_grant_doc_user_roles
AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18).
Data as JSON: /api/errors/2043742a94668548.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/models/permission-write.ts:582
create: {
workspaceId,
docId,
principalType: 'user',
principalId: userId,
role: docRoleToNew(role),
},
});
}
@Transactional()
async batchSetUserRoles(
workspaceId: string,
docId: string,
userIds: string[],
role: DocRole
) {
if (role === DocRole.Owner) {
throw new CanNotBatchGrantDocOwnerPermissions();
}
if (userIds.length === 0) {
return 0;
}
const grantRole = docRoleToNew(role);
for (const userId of userIds) {
await this.db.docGrant.upsert({
where: {
workspaceId_docId_principalType_principalId: {
workspaceId,
docId,
principalType: 'user',
principalId: userId,
},
},
update: {
role: grantRole,View on GitHub (pinned to 2af30773ae)