-
-
Notifications
You must be signed in to change notification settings - Fork 129
Description
Bug Description
createMany operations fail with an Invariant failed error when the @zenstackhq/plugin-policy is enabled and models have access control policies involving nested relation traversals.
Switching to individual create calls in a loop resolves the issue. This occurs on multiple models, not just a single one.
Environment
- ZenStack version:
@zenstackhq/orm^3.3.3,@zenstackhq/plugin-policy^3.3.3,@zenstackhq/cli^3.3.3 - Prisma version: 7.3.0
- Database: PostgreSQL
- Node.js: v22
- Integration: Using
zenstack-trpc(^0.1.22) to auto-generate CRUD routes
Schema (minimal reproduction)
The policy plugin is enabled:
plugin policy {
provider = '@zenstackhq/plugin-policy'
}Here is a simplified version of the models involved. The key aspect is that access policies traverse multiple nested relations:
model HomePage {
id String @id @default(cuid())
projectId String @unique
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
cards HomeCard[]
@@allow('all', auth().role == SUPER_ADMIN)
@@allow('all', auth().role == PARTNER_ADMIN && project.organization.partnerManagers?[userId == auth().id])
@@allow('all', auth().role == ORG_ADMIN && auth().organizationId == project.organizationId)
@@allow('all', auth().role == USER && auth().organizationId == project.organizationId)
}
model HomeCard {
id String @id @default(cuid())
title TranslatedField[] @json
subtitle TranslatedField[] @json
icon String?
mainColor String?
order Int @default(0)
type HomeCardType @default(AGENT)
homePageId String
homePage HomePage @relation(fields: [homePageId], references: [id], onDelete: Cascade)
// Policies traverse deeply nested relations: homeCard -> homePage -> project -> organization -> partnerManagers
@@allow('all', auth().role == SUPER_ADMIN)
@@allow('all', auth().role == PARTNER_ADMIN && homePage.project.organization.partnerManagers?[userId == auth().id])
@@allow('all', auth().role == ORG_ADMIN && auth().organizationId == homePage.project.organizationId)
@@allow('all', auth().role == USER && auth().organizationId == homePage.project.organizationId)
}The Project model links to Organization, which has a partnerManagers relation (a join table with userId and organizationId). So the PARTNER_ADMIN policy on HomeCard traverses 4 levels of relations: homePage.project.organization.partnerManagers?[...].
Steps to Reproduce
- Enable the policy plugin
- Define models with
@@allowpolicies that traverse multiple nested relations (3-4 levels deep) - Call
createManyon one of these models (e.g., via the auto-generated tRPC/REST endpoint)
Error
POST /generated/homeCard/createMany → 400 Bad Request
{
"error": {
"message": "Failed to execute query: Error: Invariant failed",
"code": "INTERNAL_SERVER_ERROR"
}
}
The request body contains a standard createMany payload with an array of data objects (scalar fields only, no nested relations).
Workaround
Replacing createMany with individual create calls in a loop works without any issues:
// This fails:
await db.homeCard.createMany({ data: items });
// This works:
for (const item of items) {
await db.homeCard.create({ data: item });
}Additional Context
- The issue is not specific to the HomeCard model — it happens on other models with similar deeply nested relation policies as well.
- The error message
Invariant failedis non-descriptive and makes debugging difficult. A more specific error message would be very helpful. - Based on issue #1712, ZenStack V2 internally translated
createManyto individualcreatecalls when access policies are involved. It's possible this translation has a bug in V3 when policies involve deep relation traversals. - The authenticated user context is properly set (individual
createcalls work fine with the same auth context).