payloadcms / payloadcms/payload
[plugin-multi-tenant] Race condition in auto-injected relationship field validation produces false "invalid relationship" errors
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Description
Describe the Bug
When a tenant-enabled collection is referenced from an array field on another tenant-enabled collection, creating a document with multiple rows that each reference a tenant-scoped item intermittently fails with per-row "This relationship field has an invalid relationship" validation errors — even though every referenced id exists, belongs to the correct tenant, and is otherwise valid.
The failure is non-deterministic and the rate scales with row count. With 8 rows against MongoDB it reproduces in roughly 1–4 of every 5 attempts; with more rows it approaches 100%.
Suspected root cause
The multi-tenant plugin auto-injects filterOptions on every relationship pointing at a tenant-enabled collection. Payload's relationship validator then runs validateFilterOptions, which calls payload.find to check the chosen id is in the allowed set.
Payload validates array rows in parallel via Promise.all. With N rows each holding a tenant-scoped relationship, N concurrent payload.find calls run on the same MongoDB session inside the create's transaction. MongoDB rejects all but one with:
MongoServerError: Given transaction number X does not match any in-progress transactions on session Y
validateFilterOptions swallows the error in its catch and returns an empty allow-list, so the field is reported as "invalid" for every row whose query lost the race.
This is structurally adjacent to #4078 / #4087 — that fix made validateFilterOptions honor the transaction, but didn't address N validators in the same transaction trying to use the session concurrently. The multi-tenant plugin makes this very easy to hit because it auto-injects filterOptions on every tenant-scoped relationship — the user's schema never declares one.
Link to the code that reproduces this issue
https://github.com/amitzur/payload-issue-multi-tenant-relationship-validation-race
Reproduction Steps
Run the reproducer script from the repo amitzur/payload-issue-multi-tenant-relationship-validation-race:
pnpm install
pnpm repro
Expected: the test passes — failures stays empty across all 5 trials.
Actual: the test fails — one or more trials produce per-row "This relationship field has an invalid relationship" errors.
The reproducer registers three collections via the multi-tenant plugin:
tenantsitems(tenant-enabled)holders(tenant-enabled, hasrows: arrayofrelationship→items)
It then creates one tenant + 8 items in that tenant, then attempts five times to create a holder whose rows reference all 8 items. The expectation is that all five succeed; in practice one or more fail with per-row validation errors.
Race condition proof
Uncomment the following to make the repro pass: https://github.com/amitzur/payload-issue-multi-tenant-relationship-validation-race/blob/62ba2a89245934f7d9ec59f4df1a42db377af8e1/payload.config.ts#L74
Which area(s) are affected?
db: mongodb, plugin: multi-tenant
Environment Info
Node.js: v24.12.0
payload: 3.84.1
@payloadcms/plugin-multi-tenant: 3.84.1
@payloadcms/db-mongodb: 3.84.1
Next.js: 16.2.4
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running pnpm install and pnpm repro in the linked reproducer repository, then inspect payload.config.ts around line 74 and the relationship validation path described in the issue. Done means all five holder-creation trials succeed without per-row invalid-relationship errors, with failures remaining empty.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- mongodb, typescript
- Domain
- backend, database
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100