twentyhq/twenty · error · Error

on-opportunity-partner-assigned: ${failed} person stamp(s) f

Error message

on-opportunity-partner-assigned: ${failed} person stamp(s) failed — retrying

What it means

propagatePartnerUser's assign/reassign path runs all Person partnerUser-stamps via Promise.allSettled and throws if any rejected. Same idempotent-retry design as the clear path: re-running re-collects people for the company and re-stamps only those still missing the partnerUser, so a thrown error safely drives a retry rather than leaving a partial stamp.

Source

Thrown at packages/twenty-apps/internal/twenty-partners/src/modules/opportunity/matching/services/propagate-partner-user.service.ts:141

  const companyId = after?.companyId;
  if (!companyId) return { cascaded: true, partnerUserId };

  // Don't clobber a company already owned by a DIFFERENT partner member. The single
  // partnerUser column on Company/Person models one owner per company, so reassigning it
  // here would steal the company (and its contacts) from the other partner and expose their
  // data. Stamp only the opportunity in that case and leave the company/people alone.
  const companyResult = await getCompanyPartnerUser(client, companyId);
  const companyOwner = companyResult.company?.partnerUserId;
  if (companyOwner && companyOwner !== partnerUserId) {
    return { cascaded: true, partnerUserId, companyShared: true };
  }

  await updateCompanyPartnerUser(client, companyId, partnerUserId);

  const peopleIds = await collectPeopleIds(client, { companyId: { eq: companyId } });
  const failed = await setPeoplePartnerUser(client, peopleIds, partnerUserId);
  if (failed > 0) {
    throw new Error(
      `on-opportunity-partner-assigned: ${failed} person stamp(s) failed — retrying`,
    );
  }

  return { cascaded: true, partnerUserId };
}

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. Retry — the function is idempotent (assign path re-stamps all people for the company; already-stamped rows are a no-op).
  2. If persistent, log which personIds rejected to isolate the record.
  3. Check app-server logs for 5xx or RLS denials on updatePerson.
  4. Verify the partnerUser field on Person is not field-locked for the app identity running the cascade.

Example fix

// before
const failed = await setPeoplePartnerUser(client, peopleIds, partnerUserId);
if (failed > 0) {
  throw new Error(`on-opportunity-partner-assigned: ${failed} person stamp(s) failed — retrying`);
}

// after — keep the throw for retry; log failures for diagnosis
const failedIds = await setPeoplePartnerUserDetailed(client, peopleIds, partnerUserId);
if (failedIds.length > 0) {
  console.error(`person stamp failures for ${opportunityId}/${partnerId}:`, failedIds);
  throw new Error(`on-opportunity-partner-assigned: ${failedIds.length} person stamp(s) failed — retrying`);
}
Defensive patterns

Strategy: retry

Validate before calling

// No client-side pre-validation prevents transient mid-cascade rejections.
// Ensure the workflow engine retries thrown logic-function errors.

Try / catch

// propagatePartnerUser is idempotent on the assign path too (re-stamps all
// people for the company; already-stamped rows are a no-op). Retry on throw:
for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return await propagatePartnerUser(client, params);
  } catch (err) {
    if (attempt === 2) throw err;
    await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
  }
}

Prevention

When it happens

Trigger: One or more updatePersonPartnerUser(client, id, partnerUserId) promises reject during the assign cascade. Causes: transient network/5xx; Person deleted mid-cascade; RLS field-lock; concurrent reassignment racing on the same company's people.

Common situations: Assigning a partner to an opportunity on a company with many contacts; a contact concurrently edited/deleted; app-server restart mid-batch; field-lock misconfiguration rejecting the stamp.

Related errors


AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12). Data as JSON: /api/errors/83d02051f53ca4a4. Report an issue: GitHub.