Skip to content

[Backend][fix] - FK constraint on docker image starts#1093

Merged
madster456 merged 4 commits intodevfrom
backend/fix/seed-team-member-foreign-key
Jan 9, 2026
Merged

[Backend][fix] - FK constraint on docker image starts#1093
madster456 merged 4 commits intodevfrom
backend/fix/seed-team-member-foreign-key

Conversation

@madster456
Copy link
Copy Markdown
Collaborator

@madster456 madster456 commented Jan 9, 2026

Foreign Key Constraint

When deploying Stack Auth with Docker and changing STACK_SEED_INTERNAL_PROJECT_USER_INTERNAL_ACCESS between container restarts, the seed script fails with:

PrismaClientKnownRequestError: Invalid prisma.teamMemberDirectPermission.upsert() invocation:
Foreign key constraint violated on the constraint: TeamMemberDirectPermission_tenancyId_projectUserId_teamId_fkey

This is a bug in the seed script's idempotency logic. The issue occurs in apps/backend/prisma/seed.ts (lines 296–388):

  • When admin credentials are provided, the script checks if the admin user already exists (line 297–303)
  • If the user exists, it skips the user creation block (line 305–306), which also skips creating the TeamMember record
  • However, the grantTeamPermission() call at line 382 is outside the if/else block and always runs
  • This tries to create a TeamMemberDirectPermission record, which has a foreign key constraint to TeamMember
  • If the TeamMember doesn't exist (e.g., user was created with STACK_SEED_INTERNAL_PROJECT_USER_INTERNAL_ACCESS=false previously, or the TeamMember was never created), the foreign key constraint fails.

How could this happen?

  1. Changed INTERNAL_ACCESS setting: First run with STACK_SEED_INTERNAL_PROJECT_USER_INTERNAL_ACCESS=false (user created, no TeamMember), then restarted with =true
  2. Partial seed failure/interruption: A previous seed run created the user but failed before creating the TeamMember
  3. Manual database modification: TeamMember was deleted but user still exists.

The most likely scenario would be 1 here:

Scenario 1:

  1. First deployment: STACK_SEED_INTERNAL_PROJECT_USER_INTERNAL_ACCESS=false
    • User created ✓
    • TeamMember NOT created(because adminInternalAccess=false)
  2. Second deployment: STACK_SEED_INTERNAL_PROJECT_USER_INTERNAL_ACCESS=true
    • User already exists → Skip creation
    • grantTeamPermission() called → tries to create TeamMemberDirectPermission
    • FAILS because TeamMember doesn't exist.

Solution

Add a TeamMember upsert before granting permissions when adminInternalAccess is true:

if (adminInternalAccess) {
  await internalPrisma.teamMember.upsert({
    where: {
      tenancyId_projectUserId_teamId: {
        tenancyId: internalTenancy.id,
        projectUserId: defaultUserId,
        teamId: internalTeamId,
      },
    },
    create: {
      tenancyId: internalTenancy.id,
      teamId: internalTeamId,
      projectUserId: defaultUserId,
    },
    update: {},
  });
}

This ensures the TeamMember record exists before `grantTeamPermission() is called, regardless of whether the user was just created or already existed.

Impact

  • Existing deployments: No impact. If TeamMember already exists, the upsert does nothing.
  • New deployment: Works correctly.
  • Broken deployments: This fix will repair them on the next container restart.

Testing

Tested by building a local Docker image and running the reproduction script that:

  • Starts with INTERNAL_ACCESS=false
  • Restarts with INTERNAL_ACCESS=true
  • verifies no foreign key constraint error occurs.

Summary by CodeRabbit

  • Bug Fixes
    • Made permission grants resilient to repeated seed runs by ensuring grants only apply when appropriate admin access is present.
    • Prevented duplicate team member entries during setup by making member creation idempotent, so repeated runs no longer create or alter existing records.

✏️ Tip: You can customize this high-level summary in your review settings.

@cmux-agent
Copy link
Copy Markdown

cmux-agent bot commented Jan 9, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap

Screenshot capture was skipped.

No UI changes detected - screenshots skipped


Generated by cmux preview system

@vercel
Copy link
Copy Markdown

vercel bot commented Jan 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
stack-backend Ready Ready Preview, Comment Jan 9, 2026 8:27pm
stack-dashboard Ready Ready Preview, Comment Jan 9, 2026 8:27pm
stack-demo Ready Ready Preview, Comment Jan 9, 2026 8:27pm
stack-docs Ready Ready Preview, Comment Jan 9, 2026 8:27pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

Adds an idempotent TeamMember upsert and moves the permission grant into the same adminInternalAccess-guarded branch in the database seed script; also adds comments clarifying the guard and upsert behavior.

Changes

Cohort / File(s) Summary
Database seed
apps/backend/prisma/seed.ts
Replace direct TeamMember creation with an idempotent upsert keyed on tenancyId, projectUserId, and teamId; move grantTeamPermission call into the adminInternalAccess-guarded flow; add explanatory comments about idempotency and guard behavior.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • fix team permissions #1016: Modifies apps/backend/prisma/seed.ts and permission-granting logic, affecting similar seeding and permission flows.

Poem

🐇✨ I upsert softly, so seeds won't trip,
Guarded by admin, I steady the ship,
Permissions now wait where they ought to be,
No missing links, just harmony —
A rabbit's nod to idempotency.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title '[Backend][fix] - FK constraint on docker image starts' directly references the foreign key constraint fix in the seed script, which is the core change in this PR.
Description check ✅ Passed The description is comprehensive and well-structured, detailing the bug, root cause, reproduction scenarios, the fix with code, impact analysis, and testing verification—exceeding the minimal template requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b8d814 and 7322f16.

📒 Files selected for processing (1)
  • apps/backend/prisma/seed.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors, never use toast; instead, use alerts as toasts are easily missed by the user

Files:

  • apps/backend/prisma/seed.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

NEVER use Next.js dynamic functions if avoidable; prefer using client components instead to keep pages static (e.g., use usePathname instead of await params)

Files:

  • apps/backend/prisma/seed.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use runAsynchronously or runAsynchronouslyWithAlert instead
Use ES6 maps instead of records wherever possible

Files:

  • apps/backend/prisma/seed.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Code defensively; prefer ?? throwErr(...) over non-null assertions with good error messages explicitly stating violated assumptions
Avoid the any type; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime

Files:

  • apps/backend/prisma/seed.ts
🧬 Code graph analysis (1)
apps/backend/prisma/seed.ts (1)
apps/backend/src/lib/permissions.tsx (1)
  • grantTeamPermission (97-139)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: docker
  • GitHub Check: restart-dev-and-test
  • GitHub Check: all-good
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: check_prisma_migrations (22.x)
🔇 Additional comments (2)
apps/backend/prisma/seed.ts (2)

318-319: Helpful comment explaining the deferred TeamMember creation.

This clarifies why the TeamMember creation was moved out of the else block, making it easier for future maintainers to understand the idempotency pattern.


375-401: Excellent fix for the FK constraint violation.

The idempotent upsert pattern correctly addresses the root cause:

  1. Guarantees TeamMember existence before calling grantTeamPermission, preventing the FK violation on TeamMemberDirectPermission_tenancyId_projectUserId_teamId_fkey
  2. Handles all toggle scenarios:
    • New deployment with INTERNAL_ACCESS=true: creates TeamMember + grants permissions
    • Existing deployment toggling false→true: creates missing TeamMember + grants permissions
    • Repeat runs with INTERNAL_ACCESS=true: upsert no-ops via empty update: {}
  3. Repair-on-restart: if TeamMember was manually deleted or seed was interrupted, next run recreates it

The sequencing (upsert → grant) and the adminInternalAccess guard are both correct.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@madster456 madster456 requested a review from N2D4 January 9, 2026 19:43
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Greptile Summary

This PR adds a TeamMember upsert before granting team permissions to fix a foreign key constraint error that occurs when the STACK_SEED_INTERNAL_PROJECT_USER_INTERNAL_ACCESS setting changes from false to true between container restarts.

Key changes:

  • Adds TeamMember upsert (lines 387-403) when adminInternalAccess=true to ensure the record exists before calling grantTeamPermission
  • Handles idempotency by using upsert with empty update clause

Critical issue found:

  • The grantTeamPermission call (lines 405-410) is not wrapped in an if (adminInternalAccess) check, which means it will still fail with a FK constraint error when adminInternalAccess=false (since no TeamMember record exists in that case)

Confidence Score: 3/5

  • This PR partially fixes the issue but introduces incomplete logic that can still fail
  • The PR correctly identifies and addresses the FK constraint issue when internal access changes from false to true, but the fix is incomplete. The grantTeamPermission call should be wrapped in an adminInternalAccess check to prevent FK errors when adminInternalAccess=false
  • apps/backend/prisma/seed.ts needs the grantTeamPermission call wrapped in adminInternalAccess check

Important Files Changed

File Analysis

Filename Score Overview
apps/backend/prisma/seed.ts 3/5 Adds TeamMember upsert before granting permissions, fixing FK constraint when internal access changes from false to true. However, grantTeamPermission should be wrapped in adminInternalAccess check.

Sequence Diagram

sequenceDiagram
    participant Seed as Seed Script
    participant DB as Database
    participant Check as Admin Config
    
    Seed->>Check: Check if admin credentials provided
    alt Admin credentials exist
        Seed->>DB: Query for existing admin user
        alt User already exists
            Note over Seed,DB: User exists from previous run
            Seed->>Check: Check adminInternalAccess=true
            alt adminInternalAccess is true
                Seed->>DB: Upsert TeamMember
                Note over DB: Ensures TeamMember exists<br/>before granting permissions
                DB-->>Seed: TeamMember exists
            end
            Seed->>DB: grantTeamPermission (TeamMemberDirectPermission)
            Note over Seed,DB: ⚠️ ISSUE: Not wrapped in<br/>adminInternalAccess check
            DB-->>Seed: Permission granted or FK error
        else User does not exist
            Seed->>DB: Create ProjectUser
            DB-->>Seed: User created
            Seed->>Check: Check adminInternalAccess=true
            alt adminInternalAccess is true
                Seed->>DB: Create TeamMember
                DB-->>Seed: TeamMember created
            end
            Seed->>Check: Check adminInternalAccess=true (NEW)
            alt adminInternalAccess is true
                Seed->>DB: Upsert TeamMember
                DB-->>Seed: TeamMember ensured
            end
            Seed->>DB: grantTeamPermission
            Note over Seed,DB: ⚠️ ISSUE: If adminInternalAccess=false,<br/>no TeamMember exists → FK error
            DB-->>Seed: Permission granted or FK error
        end
    end
Loading

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Jan 9, 2026

Additional Comments (1)

apps/backend/prisma/seed.ts
Wrap this in if (adminInternalAccess) block to prevent FK constraint errors.

When adminInternalAccess=false, no TeamMember record exists, so grantTeamPermission will fail with a foreign key constraint error (since TeamMemberDirectPermission has an FK to TeamMember).

  if (adminInternalAccess) {
    await grantTeamPermission(internalPrisma, {
      tenancy: internalTenancy,
      teamId: internalTeamId,
      userId: defaultUserId,
      permissionId: "team_admin",
    });
  }
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/backend/prisma/seed.ts
Line: 405:410

Comment:
Wrap this in `if (adminInternalAccess)` block to prevent FK constraint errors.

When `adminInternalAccess=false`, no `TeamMember` record exists, so `grantTeamPermission` will fail with a foreign key constraint error (since `TeamMemberDirectPermission` has an FK to `TeamMember`).

```suggestion
  if (adminInternalAccess) {
    await grantTeamPermission(internalPrisma, {
      tenancy: internalTenancy,
      teamId: internalTeamId,
      userId: defaultUserId,
      permissionId: "team_admin",
    });
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

@N2D4 N2D4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this duplicated logic now?

Copy link
Copy Markdown
Contributor

@N2D4 N2D4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ff to merge after fixing the duplicated logic

@cmux-agent
Copy link
Copy Markdown

cmux-agent bot commented Jan 9, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

@cmux-agent
Copy link
Copy Markdown

cmux-agent bot commented Jan 9, 2026

Preview Screenshots

Open Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap

Screenshot capture was skipped.

No UI changes detected - screenshots skipped


Generated by cmux preview system

@madster456 madster456 merged commit 08d986d into dev Jan 9, 2026
17 of 21 checks passed
@madster456 madster456 deleted the backend/fix/seed-team-member-foreign-key branch January 9, 2026 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants