toeverything/AFFiNE · error · Error

Cannot remove the last doc owner grant.

Error message

Cannot remove the last doc owner grant.

What it means

Thrown by DocPermissionModel.delete (permission-write.ts:646) when removing a user's grant would leave the doc with zero user-owner grants. Mirrors the workspace-owner invariant at the doc level: a doc must always retain at least one owner grant. Guarded by a SELECT ... FOR UPDATE on owner rows. Plain Error.

Source

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

      where: {
        workspaceId,
        docId,
        principalType: 'user',
        principalId: userId,
        role: 'owner',
      },
    });
    const otherOwners = await this.db.docGrant.count({
      where: {
        workspaceId,
        docId,
        principalType: 'user',
        principalId: { not: userId },
        role: 'owner',
      },
    });
    if (deletingOwner > 0 && otherOwners === 0) {
      throw new Error('Cannot remove the last doc owner grant.');
    }

    return await this.db.docGrant.deleteMany({
      where: {
        workspaceId,
        docId,
        principalType: 'user',
        principalId: userId,
      },
    });
  }
}

View on GitHub (pinned to 26c515e050)

Solutions

  1. Transfer doc ownership to another user first (grantUserRole with DocRole.Owner), then revoke the old owner.
  2. If the doc should no longer exist, delete the doc rather than removing its last owner grant.
  3. Before deleting, ensure at least one other user owner grant exists.

Example fix

// before
await models.docPermission.delete(wsId, docId, ownerUserId);
// after
await models.docPermission.grantUserRole(wsId, docId, otherUserId, DocRole.Owner);
await models.docPermission.delete(wsId, docId, ownerUserId);
Defensive patterns

Strategy: validation

Validate before calling

async function safeRemoveDocOwner(models, wsId: string, docId: string, userId: string) {
  const otherOwners = await models.db.docGrant.count({
    where: { workspaceId: wsId, docId, principalType: 'user', principalId: { not: userId }, role: 'owner' },
  });
  if (otherOwners === 0) {
    throw new Error('Refusing to remove the last doc owner; transfer ownership first.');
  }
  return models.docPermission.delete(wsId, docId, userId);
}

Prevention

When it happens

Trigger: Revoking the only remaining doc owner's grant via DocPermissionModel.delete; deleting the sole owner's doc grant; cascading a member removal that drops the last owner grant.

Common situations: Owner tries to leave a doc they alone own; a cleanup script removes all grants on a doc; ownership transfer was attempted through a code path that didn't first add a new owner.

Related errors


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