Skip to content

[BUG] createMany fails with 'Invariant failed' when policy plugin is active (V3) #2381

@olup

Description

@olup

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

  1. Enable the policy plugin
  2. Define models with @@allow policies that traverse multiple nested relations (3-4 levels deep)
  3. Call createMany on 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 failed is non-descriptive and makes debugging difficult. A more specific error message would be very helpful.
  • Based on issue #1712, ZenStack V2 internally translated createMany to individual create calls 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 create calls work fine with the same auth context).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions