twentyhq/twenty · error · Error
upsertRowLevelPermissionPredicates returned fewer than 2 pre
Error message
upsertRowLevelPermissionPredicates returned fewer than 2 predicates for opportunity OR group
What it means
Thrown by the partner RLS configuration script after it calls the metadata `upsertRowLevelPermissionPredicates` mutation to create the opportunity OR-group (predicate 0: partnerUser IS me; predicate 1: isListed = true). The mutation is expected to return both persisted predicates; if it returns fewer than two, the script aborts because the row-level security rule would be incomplete (partners would not reliably see listed briefs). It is a hard stop in a one-shot provisioning script, not a runtime user-facing path.
Source
Thrown at packages/twenty-apps/internal/twenty-partners/src/scripts/configure-partner-rls.ts:536
operand: 'IS',
workspaceMemberFieldMetadataId: workspaceMemberIdFieldId,
rowLevelPermissionPredicateGroupId: OPPORTUNITY_RLS_OR_GROUP_ID,
positionInRowLevelPermissionPredicateGroup: 0,
},
{
fieldMetadataId: opportunityIsListedFieldId,
operand: 'IS',
value: true,
rowLevelPermissionPredicateGroupId: OPPORTUNITY_RLS_OR_GROUP_ID,
positionInRowLevelPermissionPredicateGroup: 1,
},
],
} satisfies UpsertPredicatesInput,
'opportunity',
);
if (oppPredicates.length < 2) {
throw new Error(
'upsertRowLevelPermissionPredicates returned fewer than 2 predicates for opportunity OR group',
);
}
for (const predicate of oppPredicates) {
results.push(predicate);
}
console.log(
`[rls:configure] ✓ opportunity: OR group id=${OPPORTUNITY_RLS_OR_GROUP_ID} ` +
`(${oppPredicates.length} predicates: partnerUser IS me OR isListed = true)`,
);
}
// workspaceMember predicate: "id IS the current member", scoping the role's read to the
// partner's own record. Other members (e.g. an opportunity's internal owner) resolve to null.
{
const wmData = await metadataFetch<{View on GitHub (pinned to 1f5dd2bbd2)
Solutions
- Inspect the actual mutation response: log `oppPredicates` (and the full `upsertRowLevelPermissionPredicates` payload) right before the check to see exactly which predicate is missing and any partial error.
- Verify `opportunityPartnerUserFieldId` and `opportunityIsListedFieldId` resolve to live fields on the opportunity object in this workspace (query `fieldMetadata` by the universal identifiers the script uses).
- Confirm `partnerRole.id` and `opportunityObjectId` are correct for the target workspace and that RLS/row-level permission groups are enabled on the opportunity object.
- If a previous partial run left an OR group with one predicate, delete the existing `OPPORTUNITY_RLS_OR_GROUP_ID` group (or run the script's cleanup/teardown path) so the upsert is not deduping against stale state.
- Re-run the script against a clean workspace after `database:reset` so all metadata IDs match the seed.
Example fix
// before
if (oppPredicates.length < 2) {
throw new Error(
'upsertRowLevelPermissionPredicates returned fewer than 2 predicates for opportunity OR group',
);
}
// after — surface which predicate is missing before aborting
const expectedFieldIds = new Set([
opportunityPartnerUserFieldId,
opportunityIsListedFieldId,
]);
const returnedFieldIds = new Set(oppPredicates.map((p) => p.fieldMetadataId));
const missing = [...expectedFieldIds].filter((id) => !returnedFieldIds.has(id));
if (oppPredicates.length < 2) {
throw new Error(
`upsertRowLevelPermissionPredicates returned ${oppPredicates.length} predicates for opportunity OR group; missing fieldMetadataIds: ${missing.join(', ')}`,
);
} Defensive patterns
Strategy: validation
Validate before calling
// Before calling upsertPredicates for the opportunity OR group, confirm both
// fields exist on the opportunity object in this workspace.
const fieldIds = await fetchFieldMetadataIds(metadataUrl, apiKey, opportunityObjectId, [
opportunityPartnerUserFieldId,
opportunityIsListedFieldId,
]);
if (fieldIds.size !== 2) {
throw new Error(
`Cannot build opportunity OR group: missing field metadata ids on opportunity object`,
);
}
// (then proceed to the upsertPredicates call) Type guard
const isCompletePredicateSet = (
predicates: unknown,
expectedFieldIds: ReadonlyArray<string>,
): predicates is { id: string; fieldMetadataId: string }[] =>
Array.isArray(predicates) &&
predicates.length >= expectedFieldIds.length &&
expectedFieldIds.every((id) =>
(predicates as Array<{ fieldMetadataId: string }>).some(
(p) => p.fieldMetadataId === id,
),
); Prevention
- Run the configure-rls script only against a freshly reset workspace whose metadata IDs match the seed.
- Resolve field/object IDs from universal identifiers at runtime rather than hardcoding, so a regenerated workspace cannot silently mismatch.
- Before the upsert, assert all referenced fieldMetadataIds and the roleId exist.
- Make the script idempotent and clean up partial OR groups from prior failed runs before re-upserting.
When it happens
Trigger: Running `configure-partner-rls` against a workspace where (a) `opportunityPartnerUserFieldId` or `opportunityIsListedFieldId` resolves to a field that does not exist on the opportunity object, (b) the `partnerRole.id` or `opportunityObjectId` is wrong/stale, (c) RLS is not enabled on the opportunity object so the mutation silently drops predicates, or (d) the OR group UUID `OPPORTUNITY_RLS_OR_GROUP_ID` collides with an existing group and the upsert dedupes one predicate away.
Common situations: Re-running the script against a workspace whose metadata UUIDs were regenerated (e.g. after a `database:reset` or a fresh seed), pointing the script at the wrong environment, or a partially-applied earlier run that left an orphaned OR group with one predicate. Also seen after renaming the isListed/partnerUser fields without updating the ID constants the script reads.
Related errors
- upsertRowLevelPermissionPredicates returned no predicate for
- Missing ${name} env var
- Missing ${name} env var
- createFileUpload mutation did not return an upload target
- on-opportunity-partner-assigned: ${failed} person clear(s) f
AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12).
Data as JSON: /api/errors/8470408cc93ba27c.
Report an issue: GitHub.