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

Thrown by DocPermissionModel.batchSetUserRoles (permission-write.ts:584) when role === DocRole.Owner. Doc ownership is a singular, invariant-guarded relationship (a doc must always retain >=1 user owner), so it cannot be applied to a list of users in one call. UserFriendlyError, code can_not_batch_grant_doc_owner_permissions, type invalid_input, HTTP 400.

Source

Thrown at packages/backend/server/src/models/permission-write.ts:584

      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 26c515e050)

Solutions

  1. Grant Owner to exactly one user via the single-user grant path (or the doc owner-transfer endpoint).
  2. For batch operations, choose Manager / Editor / Commenter / Reader instead.
  3. Hide 'Owner' from any batch role selector in the UI.

Example fix

// before
await models.docPermission.batchSetUserRoles(wsId, docId, userIds, DocRole.Owner);
// after
await models.docPermission.batchSetUserRoles(wsId, docId, userIds, DocRole.Manager);
await models.docPermission.grantUserRole(wsId, docId, singleUserId, DocRole.Owner);
Defensive patterns

Strategy: validation

Validate before calling

function assertBatchableDocRole(role: DocRole) {
  if (role === DocRole.Owner) {
    throw new Error('Cannot batch-grant Doc Owner; grant Owner to a single user instead.');
  }
}

assertBatchableDocRole(role);
await models.docPermission.batchSetUserRoles(wsId, docId, userIds, role);

Type guard

function isBatchableDocRole(role: DocRole): boolean {
  return role !== DocRole.Owner;
}

Prevention

When it happens

Trigger: Calling the batch doc-grant API/mutation (batchSetUserRoles) with role: DocRole.Owner and any list of userIds.

Common situations: The 'add members' dialog defaults the role to Owner when a doc has no owner; a bulk-import/seed script assigns owner to many users at once; the UI reuses the batch endpoint where it should use single-owner transfer.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/2043742a94668548. Report an issue: GitHub.